paperclip 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.github/issue_template.md +3 -0
  3. data/MIGRATING-ES.md +317 -0
  4. data/MIGRATING.md +375 -0
  5. data/NEWS +17 -0
  6. data/README.md +26 -4
  7. data/UPGRADING +3 -3
  8. data/features/step_definitions/attachment_steps.rb +10 -10
  9. data/lib/paperclip.rb +1 -0
  10. data/lib/paperclip/attachment.rb +19 -6
  11. data/lib/paperclip/filename_cleaner.rb +0 -1
  12. data/lib/paperclip/geometry_detector_factory.rb +1 -1
  13. data/lib/paperclip/interpolations.rb +6 -1
  14. data/lib/paperclip/io_adapters/abstract_adapter.rb +11 -10
  15. data/lib/paperclip/io_adapters/attachment_adapter.rb +7 -1
  16. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +2 -1
  17. data/lib/paperclip/io_adapters/uri_adapter.rb +8 -6
  18. data/lib/paperclip/logger.rb +1 -1
  19. data/lib/paperclip/media_type_spoof_detector.rb +8 -5
  20. data/lib/paperclip/processor.rb +10 -2
  21. data/lib/paperclip/schema.rb +1 -1
  22. data/lib/paperclip/storage/fog.rb +1 -1
  23. data/lib/paperclip/style.rb +0 -1
  24. data/lib/paperclip/thumbnail.rb +4 -1
  25. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +4 -0
  26. data/lib/paperclip/version.rb +1 -1
  27. data/spec/paperclip/attachment_processing_spec.rb +0 -1
  28. data/spec/paperclip/attachment_spec.rb +17 -2
  29. data/spec/paperclip/filename_cleaner_spec.rb +0 -1
  30. data/spec/paperclip/integration_spec.rb +41 -5
  31. data/spec/paperclip/interpolations_spec.rb +9 -0
  32. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +28 -0
  33. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +33 -16
  34. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +56 -8
  35. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +1 -1
  36. data/spec/paperclip/media_type_spoof_detector_spec.rb +26 -0
  37. data/spec/paperclip/schema_spec.rb +46 -46
  38. data/spec/paperclip/style_spec.rb +0 -1
  39. data/spec/paperclip/thumbnail_spec.rb +5 -3
  40. data/spec/paperclip/url_generator_spec.rb +0 -1
  41. data/spec/support/model_reconstruction.rb +2 -2
  42. metadata +9 -6
data/NEWS CHANGED
@@ -1,3 +1,20 @@
1
+ 6.1.0 (2018-07-27):
2
+
3
+ * BUGFIX: Don't double-encode URLs (Roderick Monje).
4
+ * BUGFIX: Only use the content_type when it exists (Jean-Philippe Doyle).
5
+ * STABILITY: Better handling of the content-disposition header. Now supports
6
+ file name that is either enclosed or not in double quotes and is case
7
+ insensitive as per RC6266 grammar (Hasan Kumar, Yves Riel).
8
+ * STABILITY: Change database column type of attachment file size from unsigned 4-byte
9
+ `integer` to unsigned 8-byte `bigint`. The former type limits attachment size
10
+ to just over 2GB, which can easily be exceeded by a large video file (Laurent
11
+ Arnoud, Alen Zamanyan).
12
+ * STABILITY: Better error message when thumbnail processing errors (Hayden Ball).
13
+ * STABILITY: Fix file linking issues around Windows (Akihiko Odaki).
14
+ * STABILITY: Files without an extension will now be checked for spoofing attempts
15
+ (George Walters II).
16
+ * STABILITY: Manually close Tempfiles when we are done with them (Erkki Eilonen).
17
+
1
18
  6.0.0 (2018-03-09):
2
19
 
3
20
  * Improvement: Depend only on `aws-sdk-s3` instead of `aws-sdk` (https://github.com/thoughtbot/paperclip/pull/2481)
data/README.md CHANGED
@@ -1,6 +1,28 @@
1
1
  Paperclip
2
2
  =========
3
3
 
4
+ # Deprecated
5
+
6
+ **[Paperclip is deprecated]**.
7
+
8
+ For new projects, we recommend Rails' own [ActiveStorage].
9
+
10
+ For existing projects, please consult and contribute to [the migration guide] ([en español]).
11
+
12
+
13
+ We will leave the Issues open as a discussion forum _only_. We do _not_
14
+ guarantee a response from us in the Issues.
15
+
16
+ We are no longer accepting pull requests _except_ pull requests against the
17
+ migration guide. All other pull requests will be closed without merging.
18
+
19
+ [Paperclip is deprecated]: https://robots.thoughtbot.com/closing-the-trombone
20
+ [ActiveStorage]: http://guides.rubyonrails.org/active_storage_overview.html
21
+ [the migration guide]: https://github.com/thoughtbot/paperclip/blob/master/MIGRATING.md
22
+ [en español]: https://github.com/thoughtbot/paperclip/blob/master/MIGRATING-ES.md
23
+
24
+ # Existing documentation
25
+
4
26
  ## Documentation valid for `master` branch
5
27
 
6
28
  Please check the documentation for the paperclip version you are using:
@@ -167,7 +189,7 @@ Paperclip is distributed as a gem, which is how it should be used in your app.
167
189
  Include the gem in your Gemfile:
168
190
 
169
191
  ```ruby
170
- gem "paperclip", "~> 5.2.1"
192
+ gem "paperclip", "~> 6.0.0"
171
193
  ```
172
194
 
173
195
  Or, if you want to get the latest, you can get master from the main paperclip repository:
@@ -341,7 +363,7 @@ Lastly, you can also define multiple validations on a single attachment using `v
341
363
 
342
364
  ```ruby
343
365
  validates_attachment :avatar, presence: true,
344
- content_type: { content_type: "image/jpeg" },
366
+ content_type: "image/jpeg",
345
367
  size: { in: 0..10.kilobytes }
346
368
  ```
347
369
 
@@ -368,7 +390,7 @@ afterwards, then assign manually:
368
390
  ```ruby
369
391
  class Book < ActiveRecord::Base
370
392
  has_attached_file :document, styles: { thumbnail: "60x60#" }
371
- validates_attachment :document, content_type: { content_type: "application/pdf" }
393
+ validates_attachment :document, content_type: "application/pdf"
372
394
  validates_something_else # Other validations that conflict with Paperclip's
373
395
  end
374
396
 
@@ -400,7 +422,7 @@ image-y ones:
400
422
 
401
423
  ```ruby
402
424
  validates_attachment :avatar,
403
- content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }
425
+ content_type: ["image/jpeg", "image/gif", "image/png"]
404
426
  ```
405
427
 
406
428
  `Paperclip::ContentTypeDetector` will attempt to match a file's extension to an
data/UPGRADING CHANGED
@@ -2,9 +2,9 @@
2
2
  # NOTE FOR UPGRADING FROM 4.3.0 OR EARLIER #
3
3
  ##################################################
4
4
 
5
- Paperclip is now compatible with aws-sdk >= 2.0.0.
5
+ Paperclip is now compatible with aws-sdk-s3.
6
6
 
7
- If you are using S3 storage, aws-sdk >= 2.0.0 requires you to make a few small
7
+ If you are using S3 storage, aws-sdk-s3 requires you to make a few small
8
8
  changes:
9
9
 
10
10
  * You must set the `s3_region`
@@ -13,5 +13,5 @@ changes:
13
13
  using a hyphen. For example, `:public_read` needs to be changed to
14
14
  `public-read`.
15
15
 
16
- For a walkthrough of upgrading from 4 to 5 and aws-sdk >= 2.0 you can watch
16
+ For a walkthrough of upgrading from 4 to *5* (not 6) and aws-sdk >= 2.0 you can watch
17
17
  http://rubythursday.com/episodes/ruby-snack-27-upgrade-paperclip-and-aws-sdk-in-prep-for-rails-5
@@ -84,12 +84,12 @@ end
84
84
 
85
85
  Then /^I should have attachment columns for "([^"]*)"$/ do |attachment_name|
86
86
  cd(".") do
87
- columns = eval(`bundle exec rails runner "puts User.columns.map{ |column| [column.name, column.type] }.inspect"`.strip)
87
+ columns = eval(`bundle exec rails runner "puts User.columns.map{ |column| [column.name, column.sql_type] }.inspect"`.strip)
88
88
  expect_columns = [
89
- ["#{attachment_name}_file_name", :string],
90
- ["#{attachment_name}_content_type", :string],
91
- ["#{attachment_name}_file_size", :integer],
92
- ["#{attachment_name}_updated_at", :datetime]
89
+ ["#{attachment_name}_file_name", "varchar"],
90
+ ["#{attachment_name}_content_type", "varchar"],
91
+ ["#{attachment_name}_file_size", "bigint"],
92
+ ["#{attachment_name}_updated_at", "datetime"]
93
93
  ]
94
94
  expect(columns).to include(*expect_columns)
95
95
  end
@@ -97,12 +97,12 @@ end
97
97
 
98
98
  Then /^I should not have attachment columns for "([^"]*)"$/ do |attachment_name|
99
99
  cd(".") do
100
- columns = eval(`bundle exec rails runner "puts User.columns.map{ |column| [column.name, column.type] }.inspect"`.strip)
100
+ columns = eval(`bundle exec rails runner "puts User.columns.map{ |column| [column.name, column.sql_type] }.inspect"`.strip)
101
101
  expect_columns = [
102
- ["#{attachment_name}_file_name", :string],
103
- ["#{attachment_name}_content_type", :string],
104
- ["#{attachment_name}_file_size", :integer],
105
- ["#{attachment_name}_updated_at", :datetime]
102
+ ["#{attachment_name}_file_name", "varchar"],
103
+ ["#{attachment_name}_content_type", "varchar"],
104
+ ["#{attachment_name}_file_size", "bigint"],
105
+ ["#{attachment_name}_updated_at", "datetime"]
106
106
  ]
107
107
 
108
108
  expect(columns).not_to include(*expect_columns)
@@ -98,6 +98,7 @@ module Paperclip
98
98
  swallow_stderr: true,
99
99
  use_exif_orientation: true,
100
100
  whiny: true,
101
+ is_windows: Gem.win_platform?
101
102
  }
102
103
  end
103
104
 
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require 'uri'
3
2
  require 'paperclip/url_generator'
4
3
  require 'active_support/deprecation'
@@ -338,8 +337,15 @@ module Paperclip
338
337
  # inconsistencies in timing of S3 commands. It's possible that calling
339
338
  # #reprocess! will lose data if the files are not kept.
340
339
  def reprocess!(*style_args)
341
- saved_only_process, @options[:only_process] = @options[:only_process], style_args
342
- saved_preserve_files, @options[:preserve_files] = @options[:preserve_files], true
340
+ saved_flags = @options.slice(
341
+ :only_process,
342
+ :preserve_files,
343
+ :check_validity_before_processing
344
+ )
345
+ @options[:only_process] = style_args
346
+ @options[:preserve_files] = true
347
+ @options[:check_validity_before_processing] = false
348
+
343
349
  begin
344
350
  assign(self)
345
351
  save
@@ -348,8 +354,7 @@ module Paperclip
348
354
  warn "#{e} - skipping file."
349
355
  false
350
356
  ensure
351
- @options[:only_process] = saved_only_process
352
- @options[:preserve_files] = saved_preserve_files
357
+ @options.merge!(saved_flags)
353
358
  end
354
359
  end
355
360
 
@@ -532,6 +537,10 @@ module Paperclip
532
537
  reduce(original) do |file, processor|
533
538
  file = Paperclip.processor(processor).make(file, style.processor_options, self)
534
539
  intermediate_files << file unless file == @queued_for_write[:original]
540
+ # if we're processing the original, close + unlink the source tempfile
541
+ if name == :original
542
+ @queued_for_write[:original].close(true)
543
+ end
535
544
  file
536
545
  end
537
546
 
@@ -591,7 +600,11 @@ module Paperclip
591
600
  def unlink_files(files)
592
601
  Array(files).each do |file|
593
602
  file.close unless file.closed?
594
- file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
603
+
604
+ begin
605
+ file.unlink if file.respond_to?(:unlink)
606
+ rescue Errno::ENOENT
607
+ end
595
608
  end
596
609
  end
597
610
 
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Paperclip
3
2
  class FilenameCleaner
4
3
  def initialize(invalid_character_regex)
@@ -17,7 +17,7 @@ module Paperclip
17
17
  orientation = Paperclip.options[:use_exif_orientation] ?
18
18
  "%[exif:orientation]" : "1"
19
19
  Paperclip.run(
20
- "identify",
20
+ Paperclip.options[:is_windows] ? "magick identify" : "identify",
21
21
  "-format '%wx%h,#{orientation}' :file", {
22
22
  :file => "#{path}[0]"
23
23
  }, {
@@ -5,6 +5,7 @@ module Paperclip
5
5
  # Paperclip.interpolates method.
6
6
  module Interpolations
7
7
  extend self
8
+ ID_PARTITION_LIMIT = 1_000_000_000
8
9
 
9
10
  # Hash assignment of interpolations. Included only for compatibility,
10
11
  # and is not intended for normal use.
@@ -175,7 +176,11 @@ module Paperclip
175
176
  def id_partition attachment, style_name
176
177
  case id = attachment.instance.id
177
178
  when Integer
178
- ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
179
+ if id < ID_PARTITION_LIMIT
180
+ ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
181
+ else
182
+ ("%012d".freeze % id).scan(/\d{3}/).join("/".freeze)
183
+ end
179
184
  when String
180
185
  id.scan(/.{3}/).first(3).join("/".freeze)
181
186
  else
@@ -4,7 +4,7 @@ module Paperclip
4
4
  class AbstractAdapter
5
5
  OS_RESTRICTED_CHARACTERS = %r{[/:]}
6
6
 
7
- attr_reader :content_type, :original_filename, :size
7
+ attr_reader :content_type, :original_filename, :size, :tempfile
8
8
  delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
9
9
  alias :length :size
10
10
 
@@ -57,15 +57,16 @@ module Paperclip
57
57
  end
58
58
 
59
59
  def link_or_copy_file(src, dest)
60
- Paperclip.log("Trying to link #{src} to #{dest}")
61
- FileUtils.ln(src, dest, force: true) # overwrite existing
62
- @destination.close
63
- @destination.open.binmode
64
- rescue Errno::EXDEV, Errno::EPERM, Errno::ENOENT, Errno::EEXIST => e
65
- Paperclip.log(
66
- "Link failed with #{e.message}; copying link #{src} to #{dest}"
67
- )
68
- FileUtils.cp(src, dest)
60
+ begin
61
+ Paperclip.log("Trying to link #{src} to #{dest}")
62
+ FileUtils.ln(src, dest, force: true) # overwrite existing
63
+ rescue Errno::EXDEV, Errno::EPERM, Errno::ENOENT, Errno::EEXIST => e
64
+ Paperclip.log(
65
+ "Link failed with #{e.message}; copying link #{src} to #{dest}"
66
+ )
67
+ FileUtils.cp(src, dest)
68
+ end
69
+
69
70
  @destination.close
70
71
  @destination.open.binmode
71
72
  end
@@ -31,7 +31,13 @@ module Paperclip
31
31
  if source.staged?
32
32
  link_or_copy_file(source.staged_path(@style), destination.path)
33
33
  else
34
- source.copy_to_local_file(@style, destination.path)
34
+ begin
35
+ source.copy_to_local_file(@style, destination.path)
36
+ rescue Errno::EACCES
37
+ # clean up lingering tempfile if we cannot access source file
38
+ destination.close(true)
39
+ raise
40
+ end
35
41
  end
36
42
  destination
37
43
  end
@@ -9,7 +9,8 @@ module Paperclip
9
9
  REGEXP = /\Ahttps?:\/\//
10
10
 
11
11
  def initialize(target, options = {})
12
- super(URI(URI.escape(target)), options)
12
+ escaped = URI.escape(target)
13
+ super(URI(target == URI.unescape(target) ? escaped : target), options)
13
14
  end
14
15
  end
15
16
  end
@@ -28,15 +28,17 @@ module Paperclip
28
28
  end
29
29
 
30
30
  def content_type_from_content
31
- if @content.respond_to?(:content_type)
32
- @content.content_type
33
- end
31
+ @content.meta["content-type"].presence
34
32
  end
35
33
 
36
34
  def filename_from_content_disposition
37
- if @content.meta.key?("content-disposition")
38
- matches = @content.meta["content-disposition"].match(/filename="([^"]*)"/)
39
- matches[1] if matches
35
+ if @content.meta.key?("content-disposition") && @content.meta["content-disposition"].match(/filename/i)
36
+ # can include both filename and filename* values according to RCF6266. filename should come first
37
+ _, filename = @content.meta["content-disposition"].split(/filename\*?\s*=\s*/i)
38
+
39
+ # filename can be enclosed in quotes or not
40
+ matches = filename.match(/"(.*)"/)
41
+ matches ? matches[1] : filename.split(';')[0]
40
42
  end
41
43
  end
42
44
 
@@ -2,7 +2,7 @@ module Paperclip
2
2
  module Logger
3
3
  # Log a paperclip-specific line. This will log to STDOUT
4
4
  # by default. Set Paperclip.options[:log] to false to turn off.
5
- def log message
5
+ def log(message)
6
6
  logger.info("[paperclip] #{message}") if logging?
7
7
  end
8
8
 
@@ -11,7 +11,7 @@ module Paperclip
11
11
  end
12
12
 
13
13
  def spoofed?
14
- if has_name? && has_extension? && media_type_mismatch? && mapping_override_mismatch?
14
+ if has_name? && media_type_mismatch? && mapping_override_mismatch?
15
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
16
  true
17
17
  else
@@ -30,15 +30,18 @@ module Paperclip
30
30
  end
31
31
 
32
32
  def media_type_mismatch?
33
- supplied_type_mismatch? || calculated_type_mismatch?
33
+ extension_type_mismatch? || calculated_type_mismatch?
34
34
  end
35
35
 
36
- def supplied_type_mismatch?
37
- supplied_media_type.present? && !media_types_from_name.include?(supplied_media_type)
36
+ def extension_type_mismatch?
37
+ supplied_media_type.present? &&
38
+ has_extension? &&
39
+ !media_types_from_name.include?(supplied_media_type)
38
40
  end
39
41
 
40
42
  def calculated_type_mismatch?
41
- !media_types_from_name.include?(calculated_media_type)
43
+ supplied_media_type.present? &&
44
+ !calculated_content_type.include?(supplied_media_type)
42
45
  end
43
46
 
44
47
  def mapping_override_mismatch?
@@ -37,13 +37,21 @@ module Paperclip
37
37
  # The convert method runs the convert binary with the provided arguments.
38
38
  # See Paperclip.run for the available options.
39
39
  def convert(arguments = "", local_options = {})
40
- Paperclip.run('convert', arguments, local_options)
40
+ Paperclip.run(
41
+ Paperclip.options[:is_windows] ? "magick convert" : "convert",
42
+ arguments,
43
+ local_options,
44
+ )
41
45
  end
42
46
 
43
47
  # The identify method runs the identify binary with the provided arguments.
44
48
  # See Paperclip.run for the available options.
45
49
  def identify(arguments = "", local_options = {})
46
- Paperclip.run('identify', arguments, local_options)
50
+ Paperclip.run(
51
+ Paperclip.options[:is_windows] ? "magick identify" : "identify",
52
+ arguments,
53
+ local_options,
54
+ )
47
55
  end
48
56
  end
49
57
  end
@@ -5,7 +5,7 @@ module Paperclip
5
5
  module Schema
6
6
  COLUMNS = {:file_name => :string,
7
7
  :content_type => :string,
8
- :file_size => :integer,
8
+ :file_size => :bigint,
9
9
  :updated_at => :datetime}
10
10
 
11
11
  def self.included(base)
@@ -19,7 +19,7 @@ module Paperclip
19
19
  # store your files. Remember that the bucket must be unique across
20
20
  # all of Amazon S3. If the bucket does not exist, Paperclip will
21
21
  # attempt to create it.
22
- # * +fog_file*: This can be hash or lambda returning hash. The
22
+ # * +fog_file+: This can be hash or lambda returning hash. The
23
23
  # value is used as base properties for new uploaded file.
24
24
  # * +path+: This is the key under the bucket in which the file will
25
25
  # be stored. The URL will be constructed from the bucket and the
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Paperclip
3
2
  # The Style class holds the definition of a thumbnail style, applying
4
3
  # whatever processing is required to normalize the definition and delaying
@@ -85,7 +85,10 @@ module Paperclip
85
85
  dest: File.expand_path(dst.path),
86
86
  )
87
87
  rescue Terrapin::ExitStatusError => e
88
- raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
88
+ if @whiny
89
+ message = "There was an error processing the thumbnail for #{@basename}:\n" + e.message
90
+ raise Paperclip::Error, message
91
+ end
89
92
  rescue Terrapin::CommandNotFoundError => e
90
93
  raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
91
94
  end
@@ -8,6 +8,10 @@ module Paperclip
8
8
  if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
9
9
  record.errors.add(attribute, :spoofed_media_type)
10
10
  end
11
+
12
+ if adapter.tempfile
13
+ adapter.tempfile.close(true)
14
+ end
11
15
  end
12
16
  end
13
17
 
@@ -1,5 +1,5 @@
1
1
  module Paperclip
2
2
  unless defined?(Paperclip::VERSION)
3
- VERSION = "6.0.0".freeze
3
+ VERSION = "6.1.0".freeze
4
4
  end
5
5
  end