jr-paperclip 7.3.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 (200) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/custom.md +10 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/reviewdog.yml +23 -0
  7. data/.github/workflows/test.yml +46 -0
  8. data/.gitignore +19 -0
  9. data/.qlty/.gitignore +7 -0
  10. data/.qlty/qlty.toml +89 -0
  11. data/.rubocop.yml +1060 -0
  12. data/Appraisals +29 -0
  13. data/CONTRIBUTING.md +85 -0
  14. data/Gemfile +17 -0
  15. data/LICENSE +25 -0
  16. data/NEWS +567 -0
  17. data/README.md +1083 -0
  18. data/RELEASING.md +17 -0
  19. data/Rakefile +52 -0
  20. data/bin/console +11 -0
  21. data/features/basic_integration.feature +85 -0
  22. data/features/migration.feature +29 -0
  23. data/features/rake_tasks.feature +62 -0
  24. data/features/step_definitions/attachment_steps.rb +121 -0
  25. data/features/step_definitions/html_steps.rb +15 -0
  26. data/features/step_definitions/rails_steps.rb +271 -0
  27. data/features/step_definitions/s3_steps.rb +16 -0
  28. data/features/step_definitions/web_steps.rb +106 -0
  29. data/features/support/env.rb +12 -0
  30. data/features/support/file_helpers.rb +34 -0
  31. data/features/support/fixtures/boot_config.txt +15 -0
  32. data/features/support/fixtures/gemfile.txt +5 -0
  33. data/features/support/fixtures/preinitializer.txt +20 -0
  34. data/features/support/paths.rb +28 -0
  35. data/features/support/rails.rb +39 -0
  36. data/features/support/selectors.rb +19 -0
  37. data/features/support/webmock_setup.rb +8 -0
  38. data/gemfiles/7.0.gemfile +20 -0
  39. data/gemfiles/7.1.gemfile +20 -0
  40. data/gemfiles/7.2.gemfile +20 -0
  41. data/gemfiles/8.0.gemfile +20 -0
  42. data/gemfiles/8.1.gemfile +20 -0
  43. data/lib/generators/paperclip/USAGE +8 -0
  44. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  45. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  46. data/lib/jr-paperclip.rb +1 -0
  47. data/lib/paperclip/attachment.rb +634 -0
  48. data/lib/paperclip/attachment_registry.rb +60 -0
  49. data/lib/paperclip/callbacks.rb +42 -0
  50. data/lib/paperclip/content_type_detector.rb +85 -0
  51. data/lib/paperclip/errors.rb +34 -0
  52. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  53. data/lib/paperclip/filename_cleaner.rb +15 -0
  54. data/lib/paperclip/geometry.rb +157 -0
  55. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  56. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  57. data/lib/paperclip/glue.rb +18 -0
  58. data/lib/paperclip/has_attached_file.rb +116 -0
  59. data/lib/paperclip/helpers.rb +60 -0
  60. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  61. data/lib/paperclip/interpolations.rb +205 -0
  62. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  63. data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
  64. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  65. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  66. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  67. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  68. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  69. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  70. data/lib/paperclip/io_adapters/registry.rb +36 -0
  71. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  72. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  73. data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
  74. data/lib/paperclip/locales/en.yml +18 -0
  75. data/lib/paperclip/locales/fr.yml +18 -0
  76. data/lib/paperclip/locales/gd.yml +20 -0
  77. data/lib/paperclip/logger.rb +21 -0
  78. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  79. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  80. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  81. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  82. data/lib/paperclip/matchers.rb +64 -0
  83. data/lib/paperclip/media_type_spoof_detector.rb +93 -0
  84. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  85. data/lib/paperclip/processor.rb +56 -0
  86. data/lib/paperclip/processor_helpers.rb +52 -0
  87. data/lib/paperclip/rails_environment.rb +21 -0
  88. data/lib/paperclip/railtie.rb +31 -0
  89. data/lib/paperclip/schema.rb +104 -0
  90. data/lib/paperclip/storage/filesystem.rb +99 -0
  91. data/lib/paperclip/storage/fog.rb +262 -0
  92. data/lib/paperclip/storage/s3.rb +497 -0
  93. data/lib/paperclip/storage.rb +3 -0
  94. data/lib/paperclip/style.rb +106 -0
  95. data/lib/paperclip/tempfile.rb +42 -0
  96. data/lib/paperclip/tempfile_factory.rb +22 -0
  97. data/lib/paperclip/thumbnail.rb +131 -0
  98. data/lib/paperclip/url_generator.rb +83 -0
  99. data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
  100. data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
  101. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  102. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  103. data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
  104. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  105. data/lib/paperclip/validators.rb +73 -0
  106. data/lib/paperclip/version.rb +3 -0
  107. data/lib/paperclip.rb +215 -0
  108. data/lib/tasks/paperclip.rake +140 -0
  109. data/paperclip.gemspec +51 -0
  110. data/shoulda_macros/paperclip.rb +134 -0
  111. data/spec/database.yml +4 -0
  112. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  113. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  114. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  115. data/spec/paperclip/attachment_spec.rb +1617 -0
  116. data/spec/paperclip/content_type_detector_spec.rb +58 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +47 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +267 -0
  122. data/spec/paperclip/glue_spec.rb +63 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  124. data/spec/paperclip/integration_spec.rb +702 -0
  125. data/spec/paperclip/interpolations_spec.rb +270 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -0
  132. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  133. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  134. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  135. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  136. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  137. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +231 -0
  138. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  139. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  140. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  141. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  142. data/spec/paperclip/media_type_spoof_detector_spec.rb +126 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  145. data/spec/paperclip/paperclip_spec.rb +196 -0
  146. data/spec/paperclip/plural_cache_spec.rb +37 -0
  147. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  148. data/spec/paperclip/processor_spec.rb +26 -0
  149. data/spec/paperclip/rails_environment_spec.rb +30 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +298 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +102 -0
  153. data/spec/paperclip/storage/fog_spec.rb +606 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1974 -0
  156. data/spec/paperclip/style_spec.rb +251 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/tempfile_spec.rb +35 -0
  159. data/spec/paperclip/thumbnail_spec.rb +504 -0
  160. data/spec/paperclip/url_generator_spec.rb +231 -0
  161. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
  162. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
  163. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  164. data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
  165. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  166. data/spec/paperclip/validators_spec.rb +179 -0
  167. data/spec/spec_helper.rb +52 -0
  168. data/spec/support/assertions.rb +84 -0
  169. data/spec/support/fake_model.rb +24 -0
  170. data/spec/support/fake_rails.rb +12 -0
  171. data/spec/support/fixtures/12k.png +0 -0
  172. data/spec/support/fixtures/50x50.png +0 -0
  173. data/spec/support/fixtures/5k.png +0 -0
  174. data/spec/support/fixtures/animated +0 -0
  175. data/spec/support/fixtures/animated.gif +0 -0
  176. data/spec/support/fixtures/animated.unknown +0 -0
  177. data/spec/support/fixtures/aws_s3.yml +13 -0
  178. data/spec/support/fixtures/bad.png +1 -0
  179. data/spec/support/fixtures/empty.html +1 -0
  180. data/spec/support/fixtures/empty.xlsx +0 -0
  181. data/spec/support/fixtures/fog.yml +8 -0
  182. data/spec/support/fixtures/rotated.jpg +0 -0
  183. data/spec/support/fixtures/s3.yml +8 -0
  184. data/spec/support/fixtures/sample.xlsm +0 -0
  185. data/spec/support/fixtures/spaced file.jpg +0 -0
  186. data/spec/support/fixtures/spaced file.png +0 -0
  187. data/spec/support/fixtures/text.txt +1 -0
  188. data/spec/support/fixtures/twopage.pdf +0 -0
  189. data/spec/support/fixtures/uppercase.PNG +0 -0
  190. data/spec/support/matchers/accept.rb +5 -0
  191. data/spec/support/matchers/exist.rb +5 -0
  192. data/spec/support/matchers/have_column.rb +23 -0
  193. data/spec/support/mock_attachment.rb +24 -0
  194. data/spec/support/mock_interpolator.rb +24 -0
  195. data/spec/support/mock_url_generator_builder.rb +26 -0
  196. data/spec/support/model_reconstruction.rb +72 -0
  197. data/spec/support/reporting.rb +11 -0
  198. data/spec/support/test_data.rb +13 -0
  199. data/spec/support/version_helper.rb +9 -0
  200. metadata +702 -0
@@ -0,0 +1,231 @@
1
+ require "spec_helper"
2
+
3
+ describe Paperclip::UriAdapter do
4
+ let(:content_type) { "image/png" }
5
+ let(:meta) { {} }
6
+
7
+ before do
8
+ @open_return = StringIO.new("xxx")
9
+ allow(@open_return).to receive(:content_type).and_return(content_type)
10
+ allow(@open_return).to receive(:meta).and_return(meta)
11
+ Paperclip::UriAdapter.register
12
+ end
13
+
14
+ after do
15
+ Paperclip.io_adapters.unregister(described_class)
16
+ end
17
+
18
+ context "a new instance" do
19
+ let(:meta) { { "content-type" => "image/png" } }
20
+
21
+ before do
22
+ allow_any_instance_of(Paperclip::UriAdapter).
23
+ to receive(:download_content).and_return(@open_return)
24
+
25
+ @uri = URI.parse("http://thoughtbot.com/images/thoughtbot-logo.png")
26
+ @subject = Paperclip.io_adapters.for(@uri, hash_digest: Digest::MD5)
27
+ end
28
+
29
+ it "returns a file name" do
30
+ assert_equal "thoughtbot-logo.png", @subject.original_filename
31
+ end
32
+
33
+ it "closes open handle after reading" do
34
+ assert_equal true, @open_return.closed?
35
+ end
36
+
37
+ it "returns a content type" do
38
+ assert_equal "image/png", @subject.content_type
39
+ end
40
+
41
+ it "returns the size of the data" do
42
+ assert_equal @open_return.size, @subject.size
43
+ end
44
+
45
+ it "generates an MD5 hash of the contents" do
46
+ assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint
47
+ end
48
+
49
+ it "generates correct fingerprint after read" do
50
+ fingerprint = Digest::MD5.hexdigest(@subject.read)
51
+ assert_equal fingerprint, @subject.fingerprint
52
+ end
53
+
54
+ it "generates same fingerprint" do
55
+ assert_equal @subject.fingerprint, @subject.fingerprint
56
+ end
57
+
58
+ it "returns the data contained in the StringIO" do
59
+ assert_equal "xxx", @subject.read
60
+ end
61
+
62
+ it "accepts a content_type" do
63
+ @subject.content_type = "image/png"
64
+ assert_equal "image/png", @subject.content_type
65
+ end
66
+
67
+ it "accepts an original_filename" do
68
+ @subject.original_filename = "image.png"
69
+ assert_equal "image.png", @subject.original_filename
70
+ end
71
+ end
72
+
73
+ context "a directory index url" do
74
+ let(:content_type) { "text/html" }
75
+ let(:meta) { { "content-type" => "text/html" } }
76
+
77
+ before do
78
+ allow_any_instance_of(Paperclip::UriAdapter).
79
+ to receive(:download_content).and_return(@open_return)
80
+
81
+ @uri = URI.parse("http://thoughtbot.com")
82
+ @subject = Paperclip.io_adapters.for(@uri)
83
+ end
84
+
85
+ it "returns a file name" do
86
+ assert_equal "index.html", @subject.original_filename
87
+ end
88
+
89
+ it "returns a content type" do
90
+ assert_equal "text/html", @subject.content_type
91
+ end
92
+ end
93
+
94
+ context "a url with query params" do
95
+ before do
96
+ allow_any_instance_of(Paperclip::UriAdapter).
97
+ to receive(:download_content).and_return(@open_return)
98
+
99
+ @uri = URI.parse("https://github.com/thoughtbot/paperclip?file=test")
100
+ @subject = Paperclip.io_adapters.for(@uri)
101
+ end
102
+
103
+ it "returns a file name" do
104
+ assert_equal "paperclip", @subject.original_filename
105
+ end
106
+ end
107
+
108
+ context "a url with content disposition headers" do
109
+ let(:file_name) { "test_document.pdf" }
110
+ let(:filename_from_path) { "paperclip" }
111
+
112
+ before do
113
+ allow_any_instance_of(Paperclip::UriAdapter).
114
+ to receive(:download_content).and_return(@open_return)
115
+
116
+ @uri = URI.parse(
117
+ "https://github.com/thoughtbot/#{filename_from_path}?file=test"
118
+ )
119
+ end
120
+
121
+ it "returns file name from path" do
122
+ meta["content-disposition"] = "inline;"
123
+
124
+ @subject = Paperclip.io_adapters.for(@uri)
125
+
126
+ assert_equal filename_from_path, @subject.original_filename
127
+ end
128
+
129
+ it "returns a file name enclosed in double quotes" do
130
+ file_name = "john's test document.pdf"
131
+ meta["content-disposition"] = "attachment; filename=\"#{file_name}\";"
132
+
133
+ @subject = Paperclip.io_adapters.for(@uri)
134
+
135
+ assert_equal file_name, @subject.original_filename
136
+ end
137
+
138
+ it "returns a file name not enclosed in double quotes" do
139
+ meta["content-disposition"] = "ATTACHMENT; FILENAME=#{file_name};"
140
+
141
+ @subject = Paperclip.io_adapters.for(@uri)
142
+
143
+ assert_equal file_name, @subject.original_filename
144
+ end
145
+
146
+ it "does not crash when an empty filename is given" do
147
+ meta["content-disposition"] = "ATTACHMENT; FILENAME=\"\";"
148
+
149
+ @subject = Paperclip.io_adapters.for(@uri)
150
+
151
+ assert_equal "", @subject.original_filename
152
+ end
153
+
154
+ it "returns a file name ignoring RFC 5987 encoding" do
155
+ meta["content-disposition"] =
156
+ "attachment; filename=#{file_name}; filename* = utf-8''%e2%82%ac%20rates"
157
+
158
+ @subject = Paperclip.io_adapters.for(@uri)
159
+
160
+ assert_equal file_name, @subject.original_filename
161
+ end
162
+
163
+ context "when file name has consecutive periods" do
164
+ let(:file_name) { "test_document..pdf" }
165
+
166
+ it "returns a file name" do
167
+ @uri = URI.parse(
168
+ "https://github.com/thoughtbot/#{file_name}?file=test"
169
+ )
170
+ @subject = Paperclip.io_adapters.for(@uri)
171
+ assert_equal file_name, @subject.original_filename
172
+ end
173
+ end
174
+ end
175
+
176
+ context "a url with restricted characters in the filename" do
177
+ before do
178
+ allow_any_instance_of(Paperclip::UriAdapter).
179
+ to receive(:download_content).and_return(@open_return)
180
+
181
+ @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
182
+ @subject = Paperclip.io_adapters.for(@uri)
183
+ end
184
+
185
+ it "does not generate filenames that include restricted characters" do
186
+ assert_equal "paper_clip.jpg", @subject.original_filename
187
+ end
188
+
189
+ it "does not generate paths that include restricted characters" do
190
+ expect(@subject.path).to_not match(/:/)
191
+ end
192
+ end
193
+
194
+ describe "#download_content" do
195
+ before do
196
+ allowed_mock =
197
+ if RUBY_VERSION < '2.5'
198
+ allow_any_instance_of(Paperclip::UriAdapter)
199
+ else
200
+ allow(URI)
201
+ end
202
+
203
+ allowed_mock.to receive(:open).and_return(@open_return)
204
+
205
+ @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
206
+ @subject = Paperclip.io_adapters.for(@uri)
207
+ @uri_opener = RUBY_VERSION < '2.5' ? @subject : URI
208
+ end
209
+
210
+ after do
211
+ @subject.send(:download_content)
212
+ end
213
+
214
+ context "with default read_timeout" do
215
+ it "calls open without options" do
216
+ expect(@uri_opener).to receive(:open).with(@uri, {}).at_least(1).times
217
+ end
218
+ end
219
+
220
+ context "with custom read_timeout" do
221
+ before do
222
+ Paperclip.options[:read_timeout] = 120
223
+ end
224
+
225
+ it "calls open with read_timeout option" do
226
+ expect(@uri_opener)
227
+ .to receive(:open).with(@uri, { read_timeout: 120 }).at_least(1).times
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+ require "paperclip/matchers"
3
+
4
+ describe Paperclip::Shoulda::Matchers::HaveAttachedFileMatcher do
5
+ extend Paperclip::Shoulda::Matchers
6
+
7
+ it "rejects the dummy class if it has no attachment" do
8
+ reset_table "dummies"
9
+ reset_class "Dummy"
10
+ matcher = self.class.have_attached_file(:avatar)
11
+ expect(matcher).to_not accept(Dummy)
12
+ end
13
+
14
+ it "accepts the dummy class if it has an attachment" do
15
+ rebuild_model
16
+ matcher = self.class.have_attached_file(:avatar)
17
+ expect(matcher).to accept(Dummy)
18
+ end
19
+ end
@@ -0,0 +1,108 @@
1
+ require "spec_helper"
2
+ require "paperclip/matchers"
3
+
4
+ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
5
+ extend Paperclip::Shoulda::Matchers
6
+
7
+ before do
8
+ reset_table("dummies") do |d|
9
+ d.string :title
10
+ d.string :avatar_file_name
11
+ d.string :avatar_content_type
12
+ end
13
+ reset_class "Dummy"
14
+ Dummy.do_not_validate_attachment_file_type :avatar
15
+ Dummy.has_attached_file :avatar
16
+ end
17
+
18
+ it "rejects a class with no validation" do
19
+ expect(matcher).to_not accept(Dummy)
20
+ expect { matcher.failure_message }.to_not raise_error
21
+ end
22
+
23
+ it "rejects a class when the validation fails" do
24
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
25
+ expect(matcher).to_not accept(Dummy)
26
+ expect { matcher.failure_message }.to_not raise_error
27
+ end
28
+
29
+ it "accepts a class with a matching validation" do
30
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
31
+ expect(matcher).to accept(Dummy)
32
+ expect { matcher.failure_message }.to_not raise_error
33
+ end
34
+
35
+ it "accepts a class with other validations but matching types" do
36
+ Dummy.validates_presence_of :title
37
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
38
+ expect(matcher).to accept(Dummy)
39
+ expect { matcher.failure_message }.to_not raise_error
40
+ end
41
+
42
+ it "accepts a class that matches and a matcher that only specifies 'allowing'" do
43
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
44
+ matcher = plain_matcher.allowing(%w(image/png image/jpeg))
45
+
46
+ expect(matcher).to accept(Dummy)
47
+ expect { matcher.failure_message }.to_not raise_error
48
+ end
49
+
50
+ it "rejects a class that does not match and a matcher that only specifies 'allowing'" do
51
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
52
+ matcher = plain_matcher.allowing(%w(image/png image/jpeg))
53
+
54
+ expect(matcher).to_not accept(Dummy)
55
+ expect { matcher.failure_message }.to_not raise_error
56
+ end
57
+
58
+ it "accepts a class that matches and a matcher that only specifies 'rejecting'" do
59
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
60
+ matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
61
+
62
+ expect(matcher).to accept(Dummy)
63
+ expect { matcher.failure_message }.to_not raise_error
64
+ end
65
+
66
+ it "rejects a class that does not match and a matcher that only specifies 'rejecting'" do
67
+ Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
68
+ matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
69
+
70
+ expect(matcher).to_not accept(Dummy)
71
+ expect { matcher.failure_message }.to_not raise_error
72
+ end
73
+
74
+ context "using an :if to control the validation" do
75
+ before do
76
+ Dummy.class_eval do
77
+ validates_attachment_content_type :avatar, content_type: %r{image/*}, if: :go
78
+ attr_accessor :go
79
+ end
80
+ end
81
+
82
+ it "runs the validation if the control is true" do
83
+ dummy = Dummy.new
84
+ dummy.go = true
85
+ expect(matcher).to accept(dummy)
86
+ expect { matcher.failure_message }.to_not raise_error
87
+ end
88
+
89
+ it "does not run the validation if the control is false" do
90
+ dummy = Dummy.new
91
+ dummy.go = false
92
+ expect(matcher).to_not accept(dummy)
93
+ expect { matcher.failure_message }.to_not raise_error
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def plain_matcher
100
+ self.class.validate_attachment_content_type(:avatar)
101
+ end
102
+
103
+ def matcher
104
+ plain_matcher.
105
+ allowing(%w(image/png image/jpeg)).
106
+ rejecting(%w(audio/mp3 application/octet-stream))
107
+ end
108
+ end
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+ require "paperclip/matchers"
3
+
4
+ describe Paperclip::Shoulda::Matchers::ValidateAttachmentPresenceMatcher do
5
+ extend Paperclip::Shoulda::Matchers
6
+
7
+ before do
8
+ reset_table("dummies") do |d|
9
+ d.string :avatar_file_name
10
+ end
11
+ reset_class "Dummy"
12
+ Dummy.has_attached_file :avatar
13
+ Dummy.do_not_validate_attachment_file_type :avatar
14
+ end
15
+
16
+ it "rejects a class with no validation" do
17
+ expect(matcher).to_not accept(Dummy)
18
+ end
19
+
20
+ it "accepts a class with a matching validation" do
21
+ Dummy.validates_attachment_presence :avatar
22
+ expect(matcher).to accept(Dummy)
23
+ end
24
+
25
+ it "accepts an instance with other attachment validations" do
26
+ reset_table("dummies") do |d|
27
+ d.string :avatar_file_name
28
+ d.string :avatar_content_type
29
+ end
30
+ Dummy.class_eval do
31
+ validates_attachment_presence :avatar
32
+ validates_attachment_content_type :avatar, content_type: "image/gif"
33
+ end
34
+ dummy = Dummy.new
35
+
36
+ dummy.avatar = File.new fixture_file("5k.png")
37
+
38
+ expect(matcher).to accept(dummy)
39
+ end
40
+
41
+ context "using an :if to control the validation" do
42
+ before do
43
+ Dummy.class_eval do
44
+ validates_attachment_presence :avatar, if: :go
45
+ attr_accessor :go
46
+ end
47
+ end
48
+
49
+ it "runs the validation if the control is true" do
50
+ dummy = Dummy.new
51
+ dummy.avatar = nil
52
+ dummy.go = true
53
+ expect(matcher).to accept(dummy)
54
+ end
55
+
56
+ it "does not run the validation if the control is false" do
57
+ dummy = Dummy.new
58
+ dummy.avatar = nil
59
+ dummy.go = false
60
+ expect(matcher).to_not accept(dummy)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def matcher
67
+ self.class.validate_attachment_presence(:avatar)
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+ require "paperclip/matchers"
3
+
4
+ describe Paperclip::Shoulda::Matchers::ValidateAttachmentSizeMatcher do
5
+ extend Paperclip::Shoulda::Matchers
6
+
7
+ before do
8
+ reset_table("dummies") do |d|
9
+ d.string :avatar_file_name
10
+ d.bigint :avatar_file_size
11
+ end
12
+ reset_class "Dummy"
13
+ Dummy.do_not_validate_attachment_file_type :avatar
14
+ Dummy.has_attached_file :avatar
15
+ end
16
+
17
+ context "Limiting size" do
18
+ it "rejects a class with no validation" do
19
+ expect(matcher.in(256..1024)).to_not accept(Dummy)
20
+ end
21
+
22
+ it "rejects a class with a validation that's too high" do
23
+ Dummy.validates_attachment_size :avatar, in: 256..2048
24
+ expect(matcher.in(256..1024)).to_not accept(Dummy)
25
+ end
26
+
27
+ it "accepts a class with a validation that's too low" do
28
+ Dummy.validates_attachment_size :avatar, in: 0..1024
29
+ expect(matcher.in(256..1024)).to_not accept(Dummy)
30
+ end
31
+
32
+ it "accepts a class with a validation that matches" do
33
+ Dummy.validates_attachment_size :avatar, in: 256..1024
34
+ expect(matcher.in(256..1024)).to accept(Dummy)
35
+ end
36
+ end
37
+
38
+ context "allowing anything" do
39
+ it "given a class with an upper limit" do
40
+ Dummy.validates_attachment_size :avatar, less_than: 1
41
+ expect(matcher).to accept(Dummy)
42
+ end
43
+
44
+ it "given a class with a lower limit" do
45
+ Dummy.validates_attachment_size :avatar, greater_than: 1
46
+ expect(matcher).to accept(Dummy)
47
+ end
48
+ end
49
+
50
+ context "using an :if to control the validation" do
51
+ before do
52
+ Dummy.class_eval do
53
+ validates_attachment_size :avatar, greater_than: 1024, if: :go
54
+ attr_accessor :go
55
+ end
56
+ end
57
+
58
+ it "run the validation if the control is true" do
59
+ dummy = Dummy.new
60
+ dummy.go = true
61
+ expect(matcher.greater_than(1024)).to accept(dummy)
62
+ end
63
+
64
+ it "not run the validation if the control is false" do
65
+ dummy = Dummy.new
66
+ dummy.go = false
67
+ expect(matcher.greater_than(1024)).to_not accept(dummy)
68
+ end
69
+ end
70
+
71
+ context "post processing" do
72
+ before do
73
+ Dummy.validates_attachment_size :avatar, greater_than: 1024
74
+ end
75
+
76
+ it "be skipped" do
77
+ dummy = Dummy.new
78
+ expect(dummy.avatar).to_not receive(:post_process)
79
+ expect(matcher.greater_than(1024)).to accept(dummy)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def matcher
86
+ self.class.validate_attachment_size(:avatar)
87
+ end
88
+ end
@@ -0,0 +1,126 @@
1
+ require "spec_helper"
2
+
3
+ describe Paperclip::MediaTypeSpoofDetector do
4
+ it "rejects a file that is named .html and identifies as PNG" do
5
+ file = File.open(fixture_file("5k.png"))
6
+ assert Paperclip::MediaTypeSpoofDetector.using(file, "5k.html", "image/png").spoofed?
7
+ end
8
+
9
+ it "does not reject a file that is named .jpg and identifies as PNG" do
10
+ file = File.open(fixture_file("5k.png"))
11
+ assert !Paperclip::MediaTypeSpoofDetector.using(file, "5k.jpg", "image/png").spoofed?
12
+ end
13
+
14
+ it "does not reject a file that is named .html and identifies as HTML" do
15
+ file = File.open(fixture_file("empty.html"))
16
+ assert !Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "text/html").spoofed?
17
+ end
18
+
19
+ it "does not reject a file that does not have a name" do
20
+ file = File.open(fixture_file("empty.html"))
21
+ assert !Paperclip::MediaTypeSpoofDetector.using(file, "", "text/html").spoofed?
22
+ end
23
+
24
+ it "does not reject a file that does have an extension" do
25
+ file = File.open(fixture_file("empty.html"))
26
+ assert !Paperclip::MediaTypeSpoofDetector.using(file, "data", "text/html").spoofed?
27
+ end
28
+
29
+ it "does not reject when the supplied file is an IOAdapter" do
30
+ adapter = Paperclip.io_adapters.for(File.new(fixture_file("5k.png")))
31
+ assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
32
+ end
33
+
34
+ it "does not reject when the extension => content_type is in :content_type_mappings" do
35
+ file = Tempfile.open(["test", ".PEM"])
36
+ file.puts "Certificate!"
37
+ file.close
38
+
39
+ adapter = Paperclip.io_adapters.for(File.new(file.path))
40
+
41
+ begin
42
+ Paperclip.options[:content_type_mappings] = { pem: "text/plain" }
43
+ assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
44
+
45
+ # As a string.
46
+ Paperclip.options[:content_type_mappings] = { "pem" => "text/plain" }
47
+ assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
48
+ ensure
49
+ Paperclip.options[:content_type_mappings] = {}
50
+ end
51
+ end
52
+
53
+ context "file named .html and is as HTML, but we're told JPG" do
54
+ let(:file) { File.open(fixture_file("empty.html")) }
55
+ let(:spoofed?) { Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed? }
56
+
57
+ it "rejects the file" do
58
+ assert spoofed?
59
+ end
60
+
61
+ it "logs info about the detected spoof" do
62
+ expect(Paperclip).to receive(:log).with('Content Type Spoof: Filename empty.html (image/jpg from Headers, ["text/html"] from Extension), content type discovered from file command: text/html. See documentation to allow this combination.')
63
+ spoofed?
64
+ end
65
+ end
66
+
67
+ context "GIF file named without extension, but we're told GIF" do
68
+ let(:file) { File.open(fixture_file("animated")) }
69
+ let(:spoofed?) do
70
+ Paperclip::MediaTypeSpoofDetector.
71
+ using(file, "animated", "image/gif").
72
+ spoofed?
73
+ end
74
+
75
+ it "accepts the file" do
76
+ assert !spoofed?
77
+ end
78
+ end
79
+
80
+ context "GIF file named without extension, but we're told HTML" do
81
+ let(:file) { File.open(fixture_file("animated")) }
82
+ let(:spoofed?) do
83
+ Paperclip::MediaTypeSpoofDetector.
84
+ using(file, "animated", "text/html").
85
+ spoofed?
86
+ end
87
+
88
+ it "rejects the file" do
89
+ assert spoofed?
90
+ end
91
+ end
92
+
93
+ it "does not reject if content_type is empty but otherwise checks out" do
94
+ file = File.open(fixture_file("empty.html"))
95
+ assert !Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "").spoofed?
96
+ end
97
+
98
+ it "does allow array as :content_type_mappings" do
99
+ begin
100
+ Paperclip.options[:content_type_mappings] = {
101
+ html: ["binary", "text/html"]
102
+ }
103
+ file = File.open(fixture_file("empty.html"))
104
+ spoofed = Paperclip::MediaTypeSpoofDetector.
105
+ using(file, "empty.html", "text/html").spoofed?
106
+ assert !spoofed
107
+ ensure
108
+ Paperclip.options[:content_type_mappings] = {}
109
+ end
110
+ end
111
+
112
+ context "#type_from_file_command" do
113
+ let(:file) { File.new(fixture_file("empty.html")) }
114
+ let(:detector) { Paperclip::MediaTypeSpoofDetector.new(file, "html", "") }
115
+
116
+ it "does work with the output of old versions of file" do
117
+ allow(Paperclip).to receive(:run).and_return("text/html charset=us-ascii")
118
+ expect(detector.send(:type_from_file_command)).to eq("text/html")
119
+ end
120
+
121
+ it "does work with the output of new versions of file" do
122
+ allow(Paperclip).to receive(:run).and_return("text/html; charset=us-ascii")
123
+ expect(detector.send(:type_from_file_command)).to eq("text/html")
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe "Metaclasses" do
4
+ context "A meta-class of dummy" do
5
+ if active_support_version >= "4.1" || ruby_version < "2.1"
6
+ before do
7
+ rebuild_model
8
+ reset_class("Dummy")
9
+ end
10
+
11
+ it "is able to use Paperclip like a normal class" do
12
+ @dummy = Dummy.new
13
+
14
+ assert_nothing_raised do
15
+ rebuild_meta_class_of(@dummy)
16
+ end
17
+ end
18
+
19
+ it "works like any other instance" do
20
+ @dummy = Dummy.new
21
+ rebuild_meta_class_of(@dummy)
22
+
23
+ assert_nothing_raised do
24
+ @dummy.avatar = File.new(fixture_file("5k.png"), "rb")
25
+ end
26
+ assert @dummy.save
27
+ end
28
+ end
29
+ end
30
+ end