carrierwave 0.11.2 → 3.1.2

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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +495 -173
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -4
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +11 -5
  8. data/lib/carrierwave/mount.rb +217 -182
  9. data/lib/carrierwave/mounter.rb +257 -0
  10. data/lib/carrierwave/orm/activerecord.rb +29 -35
  11. data/lib/carrierwave/processing/mini_magick.rb +169 -84
  12. data/lib/carrierwave/processing/rmagick.rb +107 -25
  13. data/lib/carrierwave/processing/vips.rb +315 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +105 -87
  16. data/lib/carrierwave/storage/abstract.rb +16 -3
  17. data/lib/carrierwave/storage/file.rb +71 -3
  18. data/lib/carrierwave/storage/fog.rb +228 -57
  19. data/lib/carrierwave/storage.rb +1 -9
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +75 -45
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +84 -16
  24. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/lib/carrierwave/uploader/download.rb +4 -74
  29. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/lib/carrierwave/uploader/mountable.rb +13 -8
  33. data/lib/carrierwave/uploader/processing.rb +51 -13
  34. data/lib/carrierwave/uploader/proxy.rb +20 -9
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +2 -4
  37. data/lib/carrierwave/uploader/store.rb +85 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +175 -125
  40. data/lib/carrierwave/uploader.rb +12 -10
  41. data/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/lib/carrierwave/utilities/uri.rb +14 -12
  43. data/lib/carrierwave/utilities.rb +1 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -11
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +48 -21
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +8 -11
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +124 -86
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/el.yml +0 -11
  53. data/lib/carrierwave/locale/es.yml +0 -11
  54. data/lib/carrierwave/locale/fr.yml +0 -11
  55. data/lib/carrierwave/locale/ja.yml +0 -11
  56. data/lib/carrierwave/locale/nb.yml +0 -11
  57. data/lib/carrierwave/locale/nl.yml +0 -11
  58. data/lib/carrierwave/locale/pl.yml +0 -11
  59. data/lib/carrierwave/locale/pt-BR.yml +0 -11
  60. data/lib/carrierwave/locale/pt-PT.yml +0 -11
  61. data/lib/carrierwave/locale/ru.yml +0 -11
  62. data/lib/carrierwave/locale/sk.yml +0 -11
  63. data/lib/carrierwave/locale/tr.yml +0 -11
  64. data/lib/carrierwave/processing/mime_types.rb +0 -74
  65. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  66. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  67. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  68. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
  69. data/lib/carrierwave/utilities/deprecation.rb +0 -18
@@ -0,0 +1,315 @@
1
+ module CarrierWave
2
+
3
+ ##
4
+ # This module simplifies manipulation with vips by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'carrierwave/processing/vips'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < CarrierWave::Uploader::Base
13
+ # include CarrierWave::Vips
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < CarrierWave::Uploader::Base
19
+ # include CarrierWave::Vips
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful vips! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available Vips options at
28
+ # https://libvips.github.io/libvips/API/current/using-cli.html for more info.
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::Vips
32
+ #
33
+ # process :radial_blur => 10
34
+ #
35
+ # def radial_blur(amount)
36
+ # vips! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # === Note
45
+ #
46
+ # The ImageProcessing gem uses ruby-vips, a binding for the vips image
47
+ # library. You can find more information here:
48
+ #
49
+ # https://github.com/libvips/ruby-vips
50
+ #
51
+ #
52
+ module Vips
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ require "image_processing/vips"
57
+ # We need to disable caching since we're editing images in place.
58
+ ::Vips.cache_set_max(0)
59
+ end
60
+
61
+ module ClassMethods
62
+ def convert(format)
63
+ process :convert => format
64
+ end
65
+
66
+ def resize_to_limit(width, height)
67
+ process :resize_to_limit => [width, height]
68
+ end
69
+
70
+ def resize_to_fit(width, height)
71
+ process :resize_to_fit => [width, height]
72
+ end
73
+
74
+ def resize_to_fill(width, height, gravity='centre')
75
+ process :resize_to_fill => [width, height, gravity]
76
+ end
77
+
78
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
79
+ process :resize_and_pad => [width, height, background, gravity, alpha]
80
+ end
81
+
82
+ def crop(left, top, width, height)
83
+ process :crop => [left, top, width, height]
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Changes the image encoding format to the given format
89
+ #
90
+ # See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
91
+ #
92
+ # === Parameters
93
+ #
94
+ # [format (#to_s)] an abbreviation of the format
95
+ #
96
+ # === Yields
97
+ #
98
+ # [Vips::Image] additional manipulations to perform
99
+ #
100
+ # === Examples
101
+ #
102
+ # image.convert(:png)
103
+ #
104
+ def convert(format, page=nil)
105
+ vips! do |builder|
106
+ builder = builder.convert(format)
107
+ builder = builder.loader(page: page) if page
108
+ builder
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Resize the image to fit within the specified dimensions while retaining
114
+ # the original aspect ratio. Will only resize the image if it is larger than the
115
+ # specified dimensions. The resulting image may be shorter or narrower than specified
116
+ # in the smaller dimension but will not be larger than the specified values.
117
+ #
118
+ # === Parameters
119
+ #
120
+ # [width (Integer)] the width to scale the image to
121
+ # [height (Integer)] the height to scale the image to
122
+ # [combine_options (Hash)] additional Vips options to apply before resizing
123
+ #
124
+ # === Yields
125
+ #
126
+ # [Vips::Image] additional manipulations to perform
127
+ #
128
+ def resize_to_limit(width, height, combine_options: {})
129
+ width, height = resolve_dimensions(width, height)
130
+
131
+ vips! do |builder|
132
+ builder.resize_to_limit(width, height)
133
+ .apply(combine_options)
134
+ end
135
+ end
136
+
137
+ ##
138
+ # Resize the image to fit within the specified dimensions while retaining
139
+ # the original aspect ratio. The image may be shorter or narrower than
140
+ # specified in the smaller dimension but will not be larger than the specified values.
141
+ #
142
+ # === Parameters
143
+ #
144
+ # [width (Integer)] the width to scale the image to
145
+ # [height (Integer)] the height to scale the image to
146
+ # [combine_options (Hash)] additional Vips options to apply before resizing
147
+ #
148
+ # === Yields
149
+ #
150
+ # [Vips::Image] additional manipulations to perform
151
+ #
152
+ def resize_to_fit(width, height, combine_options: {})
153
+ width, height = resolve_dimensions(width, height)
154
+
155
+ vips! do |builder|
156
+ builder.resize_to_fit(width, height)
157
+ .apply(combine_options)
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Resize the image to fit within the specified dimensions while retaining
163
+ # the aspect ratio of the original image. If necessary, crop the image in the
164
+ # larger dimension.
165
+ #
166
+ # === Parameters
167
+ #
168
+ # [width (Integer)] the width to scale the image to
169
+ # [height (Integer)] the height to scale the image to
170
+ # [combine_options (Hash)] additional vips options to apply before resizing
171
+ #
172
+ # === Yields
173
+ #
174
+ # [Vips::Image] additional manipulations to perform
175
+ #
176
+ def resize_to_fill(width, height, _gravity = nil, combine_options: {})
177
+ width, height = resolve_dimensions(width, height)
178
+
179
+ vips! do |builder|
180
+ builder.resize_to_fill(width, height).apply(combine_options)
181
+ end
182
+ end
183
+
184
+ ##
185
+ # Resize the image to fit within the specified dimensions while retaining
186
+ # the original aspect ratio. If necessary, will pad the remaining area
187
+ # with the given color, which defaults to transparent (for gif and png,
188
+ # white for jpeg).
189
+ #
190
+ # See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
191
+ # for gravity options.
192
+ #
193
+ # === Parameters
194
+ #
195
+ # [width (Integer)] the width to scale the image to
196
+ # [height (Integer)] the height to scale the image to
197
+ # [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
198
+ # [gravity (String)] how to position the image
199
+ # [alpha (Boolean, nil)] pad the image with the alpha channel if supported
200
+ # [combine_options (Hash)] additional vips options to apply before resizing
201
+ #
202
+ # === Yields
203
+ #
204
+ # [Vips::Image] additional manipulations to perform
205
+ #
206
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
207
+ width, height = resolve_dimensions(width, height)
208
+
209
+ vips! do |builder|
210
+ builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
211
+ .apply(combine_options)
212
+ end
213
+ end
214
+
215
+ ##
216
+ # Crop the image to the contents of a box positioned at [left] and [top], with the dimensions given
217
+ # by [width] and [height]. The original image bottom/right edge is preserved if the cropping box falls
218
+ # outside the image bounds.
219
+ #
220
+ # === Parameters
221
+ #
222
+ # [left (integer)] left edge of area to extract
223
+ # [top (integer)] top edge of area to extract
224
+ # [width (Integer)] width of area to extract
225
+ # [height (Integer)] height of area to extract
226
+ #
227
+ # === Yields
228
+ #
229
+ # [Vips::Image] additional manipulations to perform
230
+ #
231
+ def crop(left, top, width, height, combine_options: {})
232
+ width, height = resolve_dimensions(width, height)
233
+ width = vips_image.width - left if width + left > vips_image.width
234
+ height = vips_image.height - top if height + top > vips_image.height
235
+
236
+ vips! do |builder|
237
+ builder.crop(left, top, width, height)
238
+ .apply(combine_options)
239
+ end
240
+ end
241
+
242
+ ##
243
+ # Returns the width of the image in pixels.
244
+ #
245
+ # === Returns
246
+ #
247
+ # [Integer] the image's width in pixels
248
+ #
249
+ def width
250
+ vips_image.width
251
+ end
252
+
253
+ ##
254
+ # Returns the height of the image in pixels.
255
+ #
256
+ # === Returns
257
+ #
258
+ # [Integer] the image's height in pixels
259
+ #
260
+ def height
261
+ vips_image.height
262
+ end
263
+
264
+ # Process the image with vip, using the ImageProcessing gem. This
265
+ # method will build a "convert" vips command and execute it on the
266
+ # current image.
267
+ #
268
+ # === Gotcha
269
+ #
270
+ # This method assumes that the object responds to +current_path+.
271
+ # Any class that this module is mixed into must have a +current_path+ method.
272
+ # CarrierWave::Uploader does, so you won't need to worry about this in
273
+ # most cases.
274
+ #
275
+ # === Yields
276
+ #
277
+ # [ImageProcessing::Builder] use it to define processing to be performed
278
+ #
279
+ # === Raises
280
+ #
281
+ # [CarrierWave::ProcessingError] if processing failed.
282
+ def vips!
283
+ builder = ImageProcessing::Vips.source(current_path)
284
+ builder = yield(builder)
285
+
286
+ result = builder.call
287
+ result.close
288
+
289
+ FileUtils.mv result.path, current_path
290
+
291
+ if File.extname(result.path) != File.extname(current_path)
292
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
293
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
294
+ file.move_to(move_to, permissions, directory_permissions)
295
+ end
296
+ rescue ::Vips::Error
297
+ message = I18n.translate(:"errors.messages.processing_error")
298
+ raise CarrierWave::ProcessingError, message
299
+ end
300
+
301
+ private
302
+
303
+ def resolve_dimensions(*dimensions)
304
+ dimensions.map do |value|
305
+ next value unless value.instance_of?(Proc)
306
+ value.arity >= 1 ? value.call(self) : value.call
307
+ end
308
+ end
309
+
310
+ def vips_image
311
+ ::Vips::Image.new_from_buffer(read, "")
312
+ end
313
+
314
+ end # Vips
315
+ end # CarrierWave
@@ -1,3 +1,3 @@
1
1
  require "carrierwave/processing/rmagick"
2
2
  require "carrierwave/processing/mini_magick"
3
- require "carrierwave/processing/mime_types"
3
+ require "carrierwave/processing/vips"
@@ -1,9 +1,5 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
- require 'active_support/core_ext/string/multibyte'
5
- require 'mime/types'
6
- require 'mimemagic'
2
+ require 'marcel'
7
3
 
8
4
  module CarrierWave
9
5
 
@@ -16,19 +12,21 @@ module CarrierWave
16
12
  # It's probably needlessly comprehensive and complex. Help is appreciated.
17
13
  #
18
14
  class SanitizedFile
15
+ include CarrierWave::Utilities::FileName
19
16
 
20
- attr_accessor :file
17
+ attr_reader :file
21
18
 
22
19
  class << self
23
20
  attr_writer :sanitize_regexp
24
21
 
25
22
  def sanitize_regexp
26
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
23
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
27
24
  end
28
25
  end
29
26
 
30
27
  def initialize(file)
31
28
  self.file = file
29
+ @content = @content_type = nil
32
30
  end
33
31
 
34
32
  ##
@@ -40,7 +38,7 @@ module CarrierWave
40
38
  #
41
39
  def original_filename
42
40
  return @original_filename if @original_filename
43
- if @file and @file.respond_to?(:original_filename)
41
+ if @file && @file.respond_to?(:original_filename)
44
42
  @file.original_filename
45
43
  elsif path
46
44
  File.basename(path)
@@ -60,29 +58,6 @@ module CarrierWave
60
58
 
61
59
  alias_method :identifier, :filename
62
60
 
63
- ##
64
- # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
65
- # this would return 'test'
66
- #
67
- # === Returns
68
- #
69
- # [String] the first part of the filename
70
- #
71
- def basename
72
- split_extension(filename)[0] if filename
73
- end
74
-
75
- ##
76
- # Returns the file extension
77
- #
78
- # === Returns
79
- #
80
- # [String] the extension
81
- #
82
- def extension
83
- split_extension(filename)[1] if filename
84
- end
85
-
86
61
  ##
87
62
  # Returns the file's size.
88
63
  #
@@ -110,12 +85,11 @@ module CarrierWave
110
85
  # [String, nil] the path where the file is located.
111
86
  #
112
87
  def path
113
- unless @file.blank?
114
- if is_path?
115
- File.expand_path(@file)
116
- elsif @file.respond_to?(:path) and not @file.path.blank?
117
- File.expand_path(@file.path)
118
- end
88
+ return if @file.blank?
89
+ if is_path?
90
+ File.expand_path(@file)
91
+ elsif @file.respond_to?(:path) && !@file.path.blank?
92
+ File.expand_path(@file.path)
119
93
  end
120
94
  end
121
95
 
@@ -134,7 +108,7 @@ module CarrierWave
134
108
  # [Boolean] whether the file is valid and has a non-zero size
135
109
  #
136
110
  def empty?
137
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
111
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
138
112
  end
139
113
 
140
114
  ##
@@ -143,8 +117,7 @@ module CarrierWave
143
117
  # [Boolean] Whether the file exists
144
118
  #
145
119
  def exists?
146
- return File.exists?(self.path) if self.path
147
- return false
120
+ self.path.present? && File.exist?(self.path)
148
121
  end
149
122
 
150
123
  ##
@@ -154,19 +127,36 @@ module CarrierWave
154
127
  #
155
128
  # [String] contents of the file
156
129
  #
157
- def read
158
- if @content
159
- @content
160
- elsif is_path?
161
- File.open(@file, "rb") {|file| file.read}
130
+ def read(*args)
131
+ if args.empty?
132
+ if @content
133
+ @content
134
+ elsif is_path?
135
+ File.open(@file, "rb") {|file| file.read }
136
+ else
137
+ @file.try(:rewind)
138
+ @content = @file.read
139
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
140
+ @content
141
+ end
162
142
  else
163
- @file.rewind if @file.respond_to?(:rewind)
164
- @content = @file.read
165
- @file.close if @file.respond_to?(:close) && @file.respond_to?(:closed?) && !@file.closed?
166
- @content
143
+ if is_path?
144
+ @file = File.open(path, "rb")
145
+ elsif @file.is_a?(CarrierWave::Uploader::Base)
146
+ @file = StringIO.new(@file.read)
147
+ end
148
+
149
+ @file.read(*args)
167
150
  end
168
151
  end
169
152
 
153
+ ##
154
+ # Rewinds the underlying file.
155
+ #
156
+ def rewind
157
+ @file.rewind if @file.respond_to?(:rewind)
158
+ end
159
+
170
160
  ##
171
161
  # Moves the file to the given path
172
162
  #
@@ -176,19 +166,26 @@ module CarrierWave
176
166
  # [permissions (Integer)] permissions to set on the file in its new location.
177
167
  # [directory_permissions (Integer)] permissions to set on created directories.
178
168
  #
179
- def move_to(new_path, permissions=nil, directory_permissions=nil)
169
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
180
170
  return if self.empty?
181
171
  new_path = File.expand_path(new_path)
182
172
 
183
173
  mkdir!(new_path, directory_permissions)
174
+ move!(new_path)
175
+ chmod!(new_path, permissions)
176
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
177
+ self
178
+ end
179
+
180
+ ##
181
+ # Helper to move file to new path.
182
+ #
183
+ def move!(new_path)
184
184
  if exists?
185
- FileUtils.mv(path, new_path) unless new_path == path
185
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
186
186
  else
187
187
  File.open(new_path, "wb") { |f| f.write(read) }
188
188
  end
189
- chmod!(new_path, permissions)
190
- self.file = new_path
191
- self
192
189
  end
193
190
 
194
191
  ##
@@ -209,13 +206,20 @@ module CarrierWave
209
206
  new_path = File.expand_path(new_path)
210
207
 
211
208
  mkdir!(new_path, directory_permissions)
209
+ copy!(new_path)
210
+ chmod!(new_path, permissions)
211
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
212
+ end
213
+
214
+ ##
215
+ # Helper to create copy of file in new path.
216
+ #
217
+ def copy!(new_path)
212
218
  if exists?
213
219
  FileUtils.cp(path, new_path) unless new_path == path
214
220
  else
215
221
  File.open(new_path, "wb") { |f| f.write(read) }
216
222
  end
217
- chmod!(new_path, permissions)
218
- self.class.new({:tempfile => new_path, :content_type => content_type})
219
223
  end
220
224
 
221
225
  ##
@@ -246,9 +250,10 @@ module CarrierWave
246
250
  #
247
251
  def content_type
248
252
  @content_type ||=
249
- existing_content_type ||
250
- mime_magic_content_type ||
251
- mime_types_content_type
253
+ identified_content_type ||
254
+ declared_content_type ||
255
+ guessed_safe_content_type ||
256
+ Marcel::MimeType::BINARY
252
257
  end
253
258
 
254
259
  ##
@@ -279,11 +284,11 @@ module CarrierWave
279
284
  if file.is_a?(Hash)
280
285
  @file = file["tempfile"] || file[:tempfile]
281
286
  @original_filename = file["filename"] || file[:filename]
282
- @content_type = file["content_type"] || file[:content_type]
287
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
283
288
  else
284
289
  @file = file
285
290
  @original_filename = nil
286
- @content_type = nil
291
+ @declared_content_type = nil
287
292
  end
288
293
  end
289
294
 
@@ -291,7 +296,7 @@ module CarrierWave
291
296
  def mkdir!(path, directory_permissions)
292
297
  options = {}
293
298
  options[:mode] = directory_permissions if directory_permissions
294
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path))
299
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
295
300
  end
296
301
 
297
302
  def chmod!(path, permissions)
@@ -300,44 +305,57 @@ module CarrierWave
300
305
 
301
306
  # Sanitize the filename, to prevent hacking
302
307
  def sanitize(name)
303
- name = name.gsub("\\", "/") # work-around for IE
308
+ name = name.scrub
309
+ name = name.tr("\\", "/") # work-around for IE
304
310
  name = File.basename(name)
305
- name = name.gsub(sanitize_regexp,"_")
311
+ name = name.gsub(sanitize_regexp, "_")
306
312
  name = "_#{name}" if name =~ /\A\.+\z/
307
- name = "unnamed" if name.size == 0
308
- return name.mb_chars.to_s
313
+ name = "unnamed" if name.size.zero?
314
+ name.to_s
309
315
  end
310
316
 
311
- def existing_content_type
312
- if @file.respond_to?(:content_type) && @file.content_type
313
- @file.content_type.to_s.chomp
314
- end
317
+ def declared_content_type
318
+ @declared_content_type ||
319
+ if @file.respond_to?(:content_type) && @file.content_type
320
+ Marcel::MimeType.for(declared_type: @file.content_type.to_s.chomp)
321
+ end
315
322
  end
316
323
 
317
- def mime_magic_content_type
318
- MimeMagic.by_magic(File.open(path)).try(:type) if path
319
- rescue Errno::ENOENT
320
- nil
321
- end
324
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
325
+ def guessed_safe_content_type
326
+ return unless path
322
327
 
323
- def mime_types_content_type
324
- ::MIME::Types.type_for(path).first.to_s if path
328
+ type = Marcel::Magic.by_path(original_filename).to_s
329
+ type if type.start_with?('text/') || type.start_with?('application/json')
325
330
  end
326
331
 
327
- def split_extension(filename)
328
- # regular expressions to try for identifying extensions
329
- extension_matchers = [
330
- /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
331
- /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
332
- ]
332
+ def identified_content_type
333
+ with_io do |io|
334
+ mimetype_by_magic = Marcel::Magic.by_magic(io)
335
+ mimetype_by_path = Marcel::Magic.by_path(path)
336
+
337
+ return nil if mimetype_by_magic.nil?
333
338
 
334
- extension_matchers.each do |regexp|
335
- if filename =~ regexp
336
- return $1, $2
339
+ if mimetype_by_path&.child_of?(mimetype_by_magic.type)
340
+ mimetype_by_path.type
341
+ else
342
+ mimetype_by_magic.type
337
343
  end
338
344
  end
339
- return filename, "" # In case we weren't able to split the extension
345
+ rescue Errno::ENOENT
346
+ nil
340
347
  end
341
348
 
349
+ def with_io(&block)
350
+ if file.is_a?(IO)
351
+ begin
352
+ yield file
353
+ ensure
354
+ file.try(:rewind)
355
+ end
356
+ elsif path
357
+ File.open(path, &block)
358
+ end
359
+ end
342
360
  end # SanitizedFile
343
361
  end # CarrierWave
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -16,7 +14,7 @@ module CarrierWave
16
14
  end
17
15
 
18
16
  def identifier
19
- uploader.filename
17
+ uploader.deduplicated_filename
20
18
  end
21
19
 
22
20
  def store!(file)
@@ -25,6 +23,21 @@ module CarrierWave
25
23
  def retrieve!(identifier)
26
24
  end
27
25
 
26
+ def cache!(new_file)
27
+ raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
28
+ end
29
+
30
+ def retrieve_from_cache!(identifier)
31
+ raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
32
+ end
33
+
34
+ def delete_dir!(path)
35
+ raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
36
+ end
37
+
38
+ def clean_cache!(seconds)
39
+ raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
40
+ end
28
41
  end # Abstract
29
42
  end # Storage
30
43
  end # CarrierWave