paperclip 4.2.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.hound.yml +1055 -0
  5. data/.rubocop.yml +1 -0
  6. data/.travis.yml +17 -15
  7. data/Appraisals +4 -16
  8. data/CONTRIBUTING.md +19 -8
  9. data/Gemfile +5 -9
  10. data/LICENSE +1 -1
  11. data/MIGRATING-ES.md +317 -0
  12. data/MIGRATING.md +375 -0
  13. data/NEWS +184 -31
  14. data/README.md +371 -201
  15. data/RELEASING.md +17 -0
  16. data/Rakefile +2 -2
  17. data/UPGRADING +12 -9
  18. data/features/basic_integration.feature +10 -6
  19. data/features/migration.feature +0 -24
  20. data/features/step_definitions/attachment_steps.rb +41 -35
  21. data/features/step_definitions/html_steps.rb +2 -2
  22. data/features/step_definitions/rails_steps.rb +39 -38
  23. data/features/step_definitions/s3_steps.rb +2 -2
  24. data/features/step_definitions/web_steps.rb +1 -103
  25. data/features/support/env.rb +1 -0
  26. data/features/support/file_helpers.rb +2 -2
  27. data/features/support/paths.rb +1 -1
  28. data/features/support/rails.rb +0 -24
  29. data/gemfiles/4.2.gemfile +6 -8
  30. data/gemfiles/5.0.gemfile +17 -0
  31. data/lib/generators/paperclip/paperclip_generator.rb +9 -1
  32. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
  33. data/lib/paperclip/attachment.rb +51 -26
  34. data/lib/paperclip/attachment_registry.rb +3 -2
  35. data/lib/paperclip/callbacks.rb +8 -6
  36. data/lib/paperclip/content_type_detector.rb +27 -11
  37. data/lib/paperclip/errors.rb +3 -1
  38. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  39. data/lib/paperclip/filename_cleaner.rb +0 -1
  40. data/lib/paperclip/geometry_detector_factory.rb +3 -3
  41. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  42. data/lib/paperclip/glue.rb +1 -1
  43. data/lib/paperclip/has_attached_file.rb +9 -2
  44. data/lib/paperclip/helpers.rb +15 -11
  45. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  46. data/lib/paperclip/interpolations.rb +24 -14
  47. data/lib/paperclip/io_adapters/abstract_adapter.rb +32 -4
  48. data/lib/paperclip/io_adapters/attachment_adapter.rb +17 -6
  49. data/lib/paperclip/io_adapters/data_uri_adapter.rb +8 -8
  50. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  51. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  52. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +8 -7
  53. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  54. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  55. data/lib/paperclip/io_adapters/registry.rb +6 -2
  56. data/lib/paperclip/io_adapters/stringio_adapter.rb +9 -6
  57. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
  58. data/lib/paperclip/io_adapters/uri_adapter.rb +43 -19
  59. data/lib/paperclip/logger.rb +1 -1
  60. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  61. data/lib/paperclip/media_type_spoof_detector.rb +13 -9
  62. data/lib/paperclip/processor.rb +15 -6
  63. data/lib/paperclip/rails_environment.rb +25 -0
  64. data/lib/paperclip/schema.rb +4 -10
  65. data/lib/paperclip/storage/filesystem.rb +13 -2
  66. data/lib/paperclip/storage/fog.rb +33 -20
  67. data/lib/paperclip/storage/s3.rb +89 -70
  68. data/lib/paperclip/style.rb +0 -1
  69. data/lib/paperclip/thumbnail.rb +24 -12
  70. data/lib/paperclip/url_generator.rb +17 -13
  71. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  72. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +4 -0
  73. data/lib/paperclip/validators.rb +1 -1
  74. data/lib/paperclip/version.rb +3 -1
  75. data/lib/paperclip.rb +27 -13
  76. data/lib/tasks/paperclip.rake +33 -3
  77. data/paperclip.gemspec +18 -15
  78. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  79. data/spec/paperclip/attachment_processing_spec.rb +2 -5
  80. data/spec/paperclip/attachment_registry_spec.rb +84 -13
  81. data/spec/paperclip/attachment_spec.rb +147 -41
  82. data/spec/paperclip/content_type_detector_spec.rb +9 -2
  83. data/spec/paperclip/file_command_content_type_detector_spec.rb +15 -2
  84. data/spec/paperclip/filename_cleaner_spec.rb +0 -1
  85. data/spec/paperclip/geometry_spec.rb +1 -1
  86. data/spec/paperclip/glue_spec.rb +44 -0
  87. data/spec/paperclip/has_attached_file_spec.rb +24 -8
  88. data/spec/paperclip/integration_spec.rb +42 -5
  89. data/spec/paperclip/interpolations_spec.rb +21 -9
  90. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +106 -23
  91. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +6 -3
  92. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +7 -1
  93. data/spec/paperclip/io_adapters/file_adapter_spec.rb +6 -3
  94. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +51 -14
  95. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +1 -1
  96. data/spec/paperclip/io_adapters/registry_spec.rb +2 -2
  97. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +5 -1
  98. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +5 -5
  99. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +126 -8
  100. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  101. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +1 -1
  102. data/spec/paperclip/media_type_spoof_detector_spec.rb +75 -11
  103. data/spec/paperclip/paperclip_spec.rb +15 -40
  104. data/spec/paperclip/plural_cache_spec.rb +17 -16
  105. data/spec/paperclip/processor_spec.rb +4 -4
  106. data/spec/paperclip/rails_environment_spec.rb +33 -0
  107. data/spec/paperclip/schema_spec.rb +46 -46
  108. data/spec/paperclip/storage/fog_spec.rb +63 -3
  109. data/spec/paperclip/storage/s3_live_spec.rb +20 -14
  110. data/spec/paperclip/storage/s3_spec.rb +400 -215
  111. data/spec/paperclip/style_spec.rb +0 -1
  112. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  113. data/spec/paperclip/tempfile_spec.rb +35 -0
  114. data/spec/paperclip/thumbnail_spec.rb +59 -38
  115. data/spec/paperclip/url_generator_spec.rb +55 -45
  116. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  117. data/spec/paperclip/validators_spec.rb +5 -5
  118. data/spec/spec_helper.rb +7 -1
  119. data/spec/support/assertions.rb +12 -1
  120. data/spec/support/fake_model.rb +4 -0
  121. data/spec/support/fixtures/empty.xlsx +0 -0
  122. data/spec/support/matchers/have_column.rb +11 -2
  123. data/spec/support/mock_attachment.rb +2 -0
  124. data/spec/support/mock_url_generator_builder.rb +2 -2
  125. data/spec/support/model_reconstruction.rb +11 -3
  126. data/spec/support/reporting.rb +11 -0
  127. metadata +110 -63
  128. data/RUNNING_TESTS.md +0 -4
  129. data/cucumber/paperclip_steps.rb +0 -6
  130. data/gemfiles/3.2.gemfile +0 -19
  131. data/gemfiles/4.0.gemfile +0 -19
  132. data/gemfiles/4.1.gemfile +0 -19
  133. data/lib/paperclip/locales/de.yml +0 -18
  134. data/lib/paperclip/locales/es.yml +0 -18
  135. data/lib/paperclip/locales/ja.yml +0 -18
  136. data/lib/paperclip/locales/pt-BR.yml +0 -18
  137. data/lib/paperclip/locales/zh-CN.yml +0 -18
  138. data/lib/paperclip/locales/zh-HK.yml +0 -18
  139. data/lib/paperclip/locales/zh-TW.yml +0 -18
  140. data/spec/support/mock_model.rb +0 -2
  141. data/spec/support/rails_helpers.rb +0 -7
@@ -5,11 +5,13 @@ 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.
11
12
  def self.[]= name, block
12
13
  define_method(name, &block)
14
+ @interpolators_cache = nil
13
15
  end
14
16
 
15
17
  # Hash access of interpolations. Included only for compatibility,
@@ -20,7 +22,7 @@ module Paperclip
20
22
 
21
23
  # Returns a sorted list of all interpolations.
22
24
  def self.all
23
- self.instance_methods(false).sort
25
+ self.instance_methods(false).sort!
24
26
  end
25
27
 
26
28
  # Perform the actual interpolation. Takes the pattern to interpolate
@@ -29,11 +31,15 @@ module Paperclip
29
31
  # an interpolation pattern for Paperclip to use.
30
32
  def self.interpolate pattern, *args
31
33
  pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol
32
- all.reverse.inject(pattern) do |result, tag|
33
- result.gsub(/:#{tag}/) do |match|
34
- send( tag, *args )
35
- end
34
+ result = pattern.dup
35
+ interpolators_cache.each do |method, token|
36
+ result.gsub!(token) { send(method, *args) } if result.include?(token)
36
37
  end
38
+ result
39
+ end
40
+
41
+ def self.interpolators_cache
42
+ @interpolators_cache ||= all.reverse!.map! { |method| [method, ":#{method}"] }
37
43
  end
38
44
 
39
45
  def self.plural_cache
@@ -42,7 +48,7 @@ module Paperclip
42
48
 
43
49
  # Returns the filename, the same way as ":basename.:extension" would.
44
50
  def filename attachment, style_name
45
- [ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".")
51
+ [ basename(attachment, style_name), extension(attachment, style_name) ].delete_if(&:empty?).join(".".freeze)
46
52
  end
47
53
 
48
54
  # Returns the interpolated URL. Will raise an error if the url itself
@@ -85,12 +91,12 @@ module Paperclip
85
91
  # all class names. Calling #class will return the expected class.
86
92
  def class attachment = nil, style_name = nil
87
93
  return super() if attachment.nil? && style_name.nil?
88
- plural_cache.underscore_and_pluralize(attachment.instance.class.to_s)
94
+ plural_cache.underscore_and_pluralize_class(attachment.instance.class)
89
95
  end
90
96
 
91
97
  # Returns the basename of the file. e.g. "file" for "file.jpg"
92
98
  def basename attachment, style_name
93
- attachment.original_filename.gsub(/#{Regexp.escape(File.extname(attachment.original_filename))}\Z/, "")
99
+ File.basename(attachment.original_filename, ".*".freeze)
94
100
  end
95
101
 
96
102
  # Returns the extension of the file. e.g. "jpg" for "file.jpg"
@@ -98,7 +104,7 @@ module Paperclip
98
104
  # of the actual extension.
99
105
  def extension attachment, style_name
100
106
  ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
101
- File.extname(attachment.original_filename).gsub(/\A\.+/, "")
107
+ File.extname(attachment.original_filename).sub(/\A\.+/, "".freeze)
102
108
  end
103
109
 
104
110
  # Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
@@ -106,7 +112,7 @@ module Paperclip
106
112
  # of the actual extension. If the extension is empty, no dot is added.
107
113
  def dotextension attachment, style_name
108
114
  ext = extension(attachment, style_name)
109
- ext.empty? ? "" : ".#{ext}"
115
+ ext.empty? ? ext : ".#{ext}"
110
116
  end
111
117
 
112
118
  # Returns an extension based on the content type. e.g. "jpeg" for
@@ -136,7 +142,7 @@ module Paperclip
136
142
  # It's possible, though unlikely, that the mime type is not in the
137
143
  # database, so just use the part after the '/' in the mime type as the
138
144
  # extension.
139
- %r{/([^/]*)\Z}.match(attachment.content_type)[1]
145
+ %r{/([^/]*)\z}.match(attachment.content_type)[1]
140
146
  end
141
147
  end
142
148
 
@@ -170,9 +176,13 @@ module Paperclip
170
176
  def id_partition attachment, style_name
171
177
  case id = attachment.instance.id
172
178
  when Integer
173
- ("%09d" % id).scan(/\d{3}/).join("/")
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
174
184
  when String
175
- ('%9.9s' % id).tr(" ", "0").scan(/.{3}/).join("/")
185
+ id.scan(/.{3}/).first(3).join("/".freeze)
176
186
  else
177
187
  nil
178
188
  end
@@ -181,7 +191,7 @@ module Paperclip
181
191
  # Returns the pluralized form of the attachment name. e.g.
182
192
  # "avatars" for an attachment of :avatar
183
193
  def attachment attachment, style_name
184
- plural_cache.pluralize(attachment.name.to_s.downcase)
194
+ plural_cache.pluralize_symbol(attachment.name)
185
195
  end
186
196
 
187
197
  # Returns the style, or the default style if nil is supplied.
@@ -4,11 +4,24 @@ module Paperclip
4
4
  class AbstractAdapter
5
5
  OS_RESTRICTED_CHARACTERS = %r{[/:]}
6
6
 
7
- attr_reader :content_type, :original_filename, :size
8
- delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
7
+ attr_reader :content_type, :original_filename, :size, :tempfile
8
+ delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
9
+ alias :length :size
10
+
11
+ def initialize(target, options = {})
12
+ @target = target
13
+ @options = options
14
+ end
9
15
 
10
16
  def fingerprint
11
- @fingerprint ||= Digest::MD5.file(path).to_s
17
+ @fingerprint ||= begin
18
+ digest = @options.fetch(:hash_digest).new
19
+ File.open(path, "rb") do |f|
20
+ buf = ""
21
+ digest.update(buf) while f.read(16384, buf)
22
+ end
23
+ digest.hexdigest
24
+ end
12
25
  end
13
26
 
14
27
  def read(length = nil, buffer = nil)
@@ -39,8 +52,23 @@ module Paperclip
39
52
  end
40
53
 
41
54
  def copy_to_tempfile(src)
42
- FileUtils.cp(src.path, destination.path)
55
+ link_or_copy_file(src.path, destination.path)
43
56
  destination
44
57
  end
58
+
59
+ def link_or_copy_file(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
+
70
+ @destination.close
71
+ @destination.open.binmode
72
+ end
45
73
  end
46
74
  end
@@ -1,6 +1,13 @@
1
1
  module Paperclip
2
2
  class AttachmentAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ Paperclip::Attachment === target || Paperclip::Style === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
4
11
  @target, @style = case target
5
12
  when Paperclip::Attachment
6
13
  [target, :original]
@@ -22,15 +29,19 @@ module Paperclip
22
29
 
23
30
  def copy_to_tempfile(source)
24
31
  if source.staged?
25
- FileUtils.cp(source.staged_path(@style), destination.path)
32
+ link_or_copy_file(source.staged_path(@style), destination.path)
26
33
  else
27
- 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
28
41
  end
29
42
  destination
30
43
  end
31
44
  end
32
45
  end
33
46
 
34
- Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
35
- Paperclip::Attachment === target || Paperclip::Style === target
36
- end
47
+ Paperclip::AttachmentAdapter.register
@@ -1,22 +1,22 @@
1
1
  module Paperclip
2
2
  class DataUriAdapter < StringioAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
3
8
 
4
9
  REGEXP = /\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m
5
10
 
6
- def initialize(target_uri)
7
- super(extract_target(target_uri))
11
+ def initialize(target_uri, options = {})
12
+ super(extract_target(target_uri), options)
8
13
  end
9
14
 
10
15
  private
11
16
 
12
17
  def extract_target(uri)
13
18
  data_uri_parts = uri.match(REGEXP) || []
14
- StringIO.new(Base64.decode64(data_uri_parts[2] || ''))
19
+ StringIO.new(Base64.decode64(data_uri_parts[2] || ""))
15
20
  end
16
-
17
21
  end
18
22
  end
19
-
20
- Paperclip.io_adapters.register Paperclip::DataUriAdapter do |target|
21
- String === target && target =~ Paperclip::DataUriAdapter::REGEXP
22
- end
@@ -1,6 +1,9 @@
1
1
  module Paperclip
2
2
  class EmptyStringAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.is_a?(String) && target.empty?
6
+ end
4
7
  end
5
8
 
6
9
  def nil?
@@ -13,6 +16,4 @@ module Paperclip
13
16
  end
14
17
  end
15
18
 
16
- Paperclip.io_adapters.register Paperclip::EmptyStringAdapter do |target|
17
- target.is_a?(String) && target.empty?
18
- end
19
+ Paperclip::EmptyStringAdapter.register
@@ -1,14 +1,22 @@
1
1
  module Paperclip
2
2
  class FileAdapter < AbstractAdapter
3
- def initialize(target)
4
- @target = target
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ File === target || ::Tempfile === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
5
11
  cache_current_values
6
12
  end
7
13
 
8
14
  private
9
15
 
10
16
  def cache_current_values
11
- self.original_filename = @target.original_filename if @target.respond_to?(:original_filename)
17
+ if @target.respond_to?(:original_filename)
18
+ self.original_filename = @target.original_filename
19
+ end
12
20
  self.original_filename ||= File.basename(@target.path)
13
21
  @tempfile = copy_to_tempfile(@target)
14
22
  @content_type = ContentTypeDetector.new(@target.path).detect
@@ -17,6 +25,4 @@ module Paperclip
17
25
  end
18
26
  end
19
27
 
20
- Paperclip.io_adapters.register Paperclip::FileAdapter do |target|
21
- File === target || Tempfile === target
22
- end
28
+ Paperclip::FileAdapter.register
@@ -1,15 +1,16 @@
1
1
  module Paperclip
2
2
  class HttpUrlProxyAdapter < UriAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
3
8
 
4
9
  REGEXP = /\Ahttps?:\/\//
5
10
 
6
- def initialize(target)
7
- super(URI(target))
11
+ def initialize(target, options = {})
12
+ escaped = URI.escape(target)
13
+ super(URI(target == URI.unescape(target) ? escaped : target), options)
8
14
  end
9
-
10
15
  end
11
16
  end
12
-
13
- Paperclip.io_adapters.register Paperclip::HttpUrlProxyAdapter do |target|
14
- String === target && target =~ Paperclip::HttpUrlProxyAdapter::REGEXP
15
- end
@@ -1,12 +1,18 @@
1
1
  module Paperclip
2
2
  class IdentityAdapter < AbstractAdapter
3
- def new(adapter)
4
- adapter
3
+ def self.register
4
+ Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|
5
+ Paperclip.io_adapters.registered?(target)
6
+ end
7
+ end
8
+
9
+ def initialize
5
10
  end
6
- end
7
- end
8
11
 
9
- Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|
10
- Paperclip.io_adapters.registered?(target)
12
+ def new(target, _)
13
+ target
14
+ end
15
+ end
11
16
  end
12
17
 
18
+ Paperclip::IdentityAdapter.register
@@ -1,8 +1,13 @@
1
1
  module Paperclip
2
2
  class NilAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.nil? || ((Paperclip::Attachment === target) && !target.present?)
6
+ end
4
7
  end
5
8
 
9
+ def initialize(_target, _options = {}); end
10
+
6
11
  def original_filename
7
12
  ""
8
13
  end
@@ -19,7 +24,7 @@ module Paperclip
19
24
  true
20
25
  end
21
26
 
22
- def read(*args)
27
+ def read(*_args)
23
28
  nil
24
29
  end
25
30
 
@@ -29,6 +34,4 @@ module Paperclip
29
34
  end
30
35
  end
31
36
 
32
- Paperclip.io_adapters.register Paperclip::NilAdapter do |target|
33
- target.nil? || ( (Paperclip::Attachment === target) && !target.present? )
34
- end
37
+ Paperclip::NilAdapter.register
@@ -12,6 +12,10 @@ module Paperclip
12
12
  @registered_handlers << [block, handler_class]
13
13
  end
14
14
 
15
+ def unregister(handler_class)
16
+ @registered_handlers.reject! { |_, klass| klass == handler_class }
17
+ end
18
+
15
19
  def handler_for(target)
16
20
  @registered_handlers.each do |tester, handler|
17
21
  return handler if tester.call(target)
@@ -25,8 +29,8 @@ module Paperclip
25
29
  end
26
30
  end
27
31
 
28
- def for(target)
29
- handler_for(target).new(target)
32
+ def for(target, options = {})
33
+ handler_for(target).new(target, options)
30
34
  end
31
35
  end
32
36
  end
@@ -1,7 +1,13 @@
1
1
  module Paperclip
2
2
  class StringioAdapter < AbstractAdapter
3
- def initialize(target)
4
- @target = target
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ StringIO === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
5
11
  cache_current_values
6
12
  end
7
13
 
@@ -24,10 +30,7 @@ module Paperclip
24
30
  destination.rewind
25
31
  destination
26
32
  end
27
-
28
33
  end
29
34
  end
30
35
 
31
- Paperclip.io_adapters.register Paperclip::StringioAdapter do |target|
32
- StringIO === target
33
- end
36
+ Paperclip::StringioAdapter.register
@@ -1,7 +1,13 @@
1
1
  module Paperclip
2
2
  class UploadedFileAdapter < AbstractAdapter
3
- def initialize(target)
4
- @target = target
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.class.name.include?("UploadedFile")
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
5
11
  cache_current_values
6
12
 
7
13
  if @target.respond_to?(:tempfile)
@@ -24,7 +30,7 @@ module Paperclip
24
30
  end
25
31
 
26
32
  def content_type_detector
27
- self.class.content_type_detector
33
+ self.class.content_type_detector || Paperclip::ContentTypeDetector
28
34
  end
29
35
 
30
36
  def determine_content_type
@@ -37,6 +43,4 @@ module Paperclip
37
43
  end
38
44
  end
39
45
 
40
- Paperclip.io_adapters.register Paperclip::UploadedFileAdapter do |target|
41
- target.class.name.include?("UploadedFile")
42
- end
46
+ Paperclip::UploadedFileAdapter.register
@@ -1,35 +1,63 @@
1
- require 'open-uri'
1
+ require "open-uri"
2
2
 
3
3
  module Paperclip
4
4
  class UriAdapter < AbstractAdapter
5
- def initialize(target)
6
- @target = target
5
+ attr_writer :content_type
6
+
7
+ def self.register
8
+ Paperclip.io_adapters.register self do |target|
9
+ target.is_a?(URI)
10
+ end
11
+ end
12
+
13
+ def initialize(target, options = {})
14
+ super
7
15
  @content = download_content
8
16
  cache_current_values
9
17
  @tempfile = copy_to_tempfile(@content)
10
18
  end
11
19
 
12
- attr_writer :content_type
13
-
14
20
  private
15
21
 
16
- def download_content
17
- open(@target)
22
+ def cache_current_values
23
+ self.content_type = content_type_from_content || "text/html"
24
+
25
+ self.original_filename = filename_from_content_disposition ||
26
+ filename_from_path || default_filename
27
+ @size = @content.size
18
28
  end
19
29
 
20
- def cache_current_values
21
- @original_filename = @target.path.split("/").last
22
- @original_filename ||= "index.html"
23
- self.original_filename = @original_filename.strip
30
+ def content_type_from_content
31
+ @content.meta["content-type"].presence
32
+ end
24
33
 
25
- @content_type = @content.content_type if @content.respond_to?(:content_type)
26
- @content_type ||= "text/html"
34
+ def filename_from_content_disposition
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)
27
38
 
28
- @size = @content.size
39
+ # filename can be enclosed in quotes or not
40
+ matches = filename.match(/"(.*)"/)
41
+ matches ? matches[1] : filename.split(';')[0]
42
+ end
43
+ end
44
+
45
+ def filename_from_path
46
+ @target.path.split("/").last
47
+ end
48
+
49
+ def default_filename
50
+ "index.html"
51
+ end
52
+
53
+ def download_content
54
+ options = { read_timeout: Paperclip.options[:read_timeout] }.compact
55
+
56
+ open(@target, **options)
29
57
  end
30
58
 
31
59
  def copy_to_tempfile(src)
32
- while data = src.read(16*1024)
60
+ while data = src.read(16 * 1024)
33
61
  destination.write(data)
34
62
  end
35
63
  src.close
@@ -38,7 +66,3 @@ module Paperclip
38
66
  end
39
67
  end
40
68
  end
41
-
42
- Paperclip.io_adapters.register Paperclip::UriAdapter do |target|
43
- target.kind_of?(URI)
44
- end
@@ -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
 
@@ -40,9 +40,9 @@ module Paperclip
40
40
 
41
41
  def failure_message
42
42
  "#{expected_attachment}\n".tap do |message|
43
- message << accepted_types_and_failures
43
+ message << accepted_types_and_failures.to_s
44
44
  message << "\n\n" if @allowed_types.present? && @rejected_types.present?
45
- message << rejected_types_and_failures
45
+ message << rejected_types_and_failures.to_s
46
46
  end
47
47
  end
48
48
 
@@ -55,7 +55,7 @@ module Paperclip
55
55
  def accepted_types_and_failures
56
56
  if @allowed_types.present?
57
57
  "Accept content types: #{@allowed_types.join(", ")}\n".tap do |message|
58
- if @missing_allowed_types.any?
58
+ if @missing_allowed_types.present?
59
59
  message << " #{@missing_allowed_types.join(", ")} were rejected."
60
60
  else
61
61
  message << " All were accepted successfully."
@@ -66,7 +66,7 @@ module Paperclip
66
66
  def rejected_types_and_failures
67
67
  if @rejected_types.present?
68
68
  "Reject content types: #{@rejected_types.join(", ")}\n".tap do |message|
69
- if @missing_rejected_types.any?
69
+ if @missing_rejected_types.present?
70
70
  message << " #{@missing_rejected_types.join(", ")} were accepted."
71
71
  else
72
72
  message << " All were rejected successfully."
@@ -11,8 +11,8 @@ module Paperclip
11
11
  end
12
12
 
13
13
  def spoofed?
14
- if has_name? && has_extension? && 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} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.")
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
16
  true
17
17
  else
18
18
  false
@@ -30,19 +30,22 @@ 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?
45
- mapped_content_type != calculated_content_type
48
+ !Array(mapped_content_type).include?(calculated_content_type)
46
49
  end
47
50
 
48
51
 
@@ -72,8 +75,9 @@ module Paperclip
72
75
 
73
76
  def type_from_file_command
74
77
  begin
75
- Paperclip.run("file", "-b --mime :file", :file => @file.path).split(/[:;]\s+/).first
76
- rescue Cocaine::CommandLineError
78
+ Paperclip.run("file", "-b --mime :file", file: @file.path).
79
+ split(/[:;\s]+/).first
80
+ rescue Terrapin::CommandLineError
77
81
  ""
78
82
  end
79
83
  end