paperclip 6.0.0 → 6.1.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 (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