carrierwave 0.11.2 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +452 -178
  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 +212 -182
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +22 -33
  11. data/lib/carrierwave/processing/mini_magick.rb +140 -84
  12. data/lib/carrierwave/processing/rmagick.rb +72 -21
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +83 -84
  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 +215 -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 +80 -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 +48 -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 +59 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +170 -122
  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 +39 -21
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +132 -80
  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
@@ -1,9 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
2
  require 'active_support/core_ext/string/multibyte'
5
- require 'mime/types'
6
- require 'mimemagic'
3
+ require 'marcel'
7
4
 
8
5
  module CarrierWave
9
6
 
@@ -16,19 +13,21 @@ module CarrierWave
16
13
  # It's probably needlessly comprehensive and complex. Help is appreciated.
17
14
  #
18
15
  class SanitizedFile
16
+ include CarrierWave::Utilities::FileName
19
17
 
20
- attr_accessor :file
18
+ attr_reader :file
21
19
 
22
20
  class << self
23
21
  attr_writer :sanitize_regexp
24
22
 
25
23
  def sanitize_regexp
26
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
24
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
27
25
  end
28
26
  end
29
27
 
30
28
  def initialize(file)
31
29
  self.file = file
30
+ @content = @content_type = nil
32
31
  end
33
32
 
34
33
  ##
@@ -40,7 +39,7 @@ module CarrierWave
40
39
  #
41
40
  def original_filename
42
41
  return @original_filename if @original_filename
43
- if @file and @file.respond_to?(:original_filename)
42
+ if @file && @file.respond_to?(:original_filename)
44
43
  @file.original_filename
45
44
  elsif path
46
45
  File.basename(path)
@@ -60,29 +59,6 @@ module CarrierWave
60
59
 
61
60
  alias_method :identifier, :filename
62
61
 
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
62
  ##
87
63
  # Returns the file's size.
88
64
  #
@@ -110,12 +86,11 @@ module CarrierWave
110
86
  # [String, nil] the path where the file is located.
111
87
  #
112
88
  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
89
+ return if @file.blank?
90
+ if is_path?
91
+ File.expand_path(@file)
92
+ elsif @file.respond_to?(:path) && !@file.path.blank?
93
+ File.expand_path(@file.path)
119
94
  end
120
95
  end
121
96
 
@@ -134,7 +109,7 @@ module CarrierWave
134
109
  # [Boolean] whether the file is valid and has a non-zero size
135
110
  #
136
111
  def empty?
137
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
112
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
138
113
  end
139
114
 
140
115
  ##
@@ -143,8 +118,7 @@ module CarrierWave
143
118
  # [Boolean] Whether the file exists
144
119
  #
145
120
  def exists?
146
- return File.exists?(self.path) if self.path
147
- return false
121
+ self.path.present? && File.exist?(self.path)
148
122
  end
149
123
 
150
124
  ##
@@ -154,15 +128,21 @@ module CarrierWave
154
128
  #
155
129
  # [String] contents of the file
156
130
  #
157
- def read
131
+ def read(*args)
158
132
  if @content
159
- @content
133
+ if args.empty?
134
+ @content
135
+ else
136
+ length, outbuf = args
137
+ raise ArgumentError, "outbuf argument not supported since the content is already loaded" if outbuf
138
+ @content[0, length]
139
+ end
160
140
  elsif is_path?
161
- File.open(@file, "rb") {|file| file.read}
141
+ File.open(@file, "rb") {|file| file.read(*args)}
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?
143
+ @file.try(:rewind)
144
+ @content = @file.read(*args)
145
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
166
146
  @content
167
147
  end
168
148
  end
@@ -176,19 +156,26 @@ module CarrierWave
176
156
  # [permissions (Integer)] permissions to set on the file in its new location.
177
157
  # [directory_permissions (Integer)] permissions to set on created directories.
178
158
  #
179
- def move_to(new_path, permissions=nil, directory_permissions=nil)
159
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
180
160
  return if self.empty?
181
161
  new_path = File.expand_path(new_path)
182
162
 
183
163
  mkdir!(new_path, directory_permissions)
164
+ move!(new_path)
165
+ chmod!(new_path, permissions)
166
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
167
+ self
168
+ end
169
+
170
+ ##
171
+ # Helper to move file to new path.
172
+ #
173
+ def move!(new_path)
184
174
  if exists?
185
- FileUtils.mv(path, new_path) unless new_path == path
175
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
186
176
  else
187
177
  File.open(new_path, "wb") { |f| f.write(read) }
188
178
  end
189
- chmod!(new_path, permissions)
190
- self.file = new_path
191
- self
192
179
  end
193
180
 
194
181
  ##
@@ -209,13 +196,20 @@ module CarrierWave
209
196
  new_path = File.expand_path(new_path)
210
197
 
211
198
  mkdir!(new_path, directory_permissions)
199
+ copy!(new_path)
200
+ chmod!(new_path, permissions)
201
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
202
+ end
203
+
204
+ ##
205
+ # Helper to create copy of file in new path.
206
+ #
207
+ def copy!(new_path)
212
208
  if exists?
213
209
  FileUtils.cp(path, new_path) unless new_path == path
214
210
  else
215
211
  File.open(new_path, "wb") { |f| f.write(read) }
216
212
  end
217
- chmod!(new_path, permissions)
218
- self.class.new({:tempfile => new_path, :content_type => content_type})
219
213
  end
220
214
 
221
215
  ##
@@ -246,9 +240,10 @@ module CarrierWave
246
240
  #
247
241
  def content_type
248
242
  @content_type ||=
249
- existing_content_type ||
250
- mime_magic_content_type ||
251
- mime_types_content_type
243
+ identified_content_type ||
244
+ declared_content_type ||
245
+ guessed_safe_content_type ||
246
+ Marcel::MimeType::BINARY
252
247
  end
253
248
 
254
249
  ##
@@ -279,11 +274,11 @@ module CarrierWave
279
274
  if file.is_a?(Hash)
280
275
  @file = file["tempfile"] || file[:tempfile]
281
276
  @original_filename = file["filename"] || file[:filename]
282
- @content_type = file["content_type"] || file[:content_type]
277
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
283
278
  else
284
279
  @file = file
285
280
  @original_filename = nil
286
- @content_type = nil
281
+ @declared_content_type = nil
287
282
  end
288
283
  end
289
284
 
@@ -291,7 +286,7 @@ module CarrierWave
291
286
  def mkdir!(path, directory_permissions)
292
287
  options = {}
293
288
  options[:mode] = directory_permissions if directory_permissions
294
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exists?(File.dirname(path))
289
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
295
290
  end
296
291
 
297
292
  def chmod!(path, permissions)
@@ -300,44 +295,48 @@ module CarrierWave
300
295
 
301
296
  # Sanitize the filename, to prevent hacking
302
297
  def sanitize(name)
303
- name = name.gsub("\\", "/") # work-around for IE
298
+ name = name.scrub
299
+ name = name.tr("\\", "/") # work-around for IE
304
300
  name = File.basename(name)
305
- name = name.gsub(sanitize_regexp,"_")
301
+ name = name.gsub(sanitize_regexp, "_")
306
302
  name = "_#{name}" if name =~ /\A\.+\z/
307
- name = "unnamed" if name.size == 0
308
- return name.mb_chars.to_s
303
+ name = "unnamed" if name.size.zero?
304
+ name.mb_chars.to_s
309
305
  end
310
306
 
311
- def existing_content_type
312
- if @file.respond_to?(:content_type) && @file.content_type
313
- @file.content_type.to_s.chomp
314
- end
307
+ def declared_content_type
308
+ @declared_content_type ||
309
+ if @file.respond_to?(:content_type) && @file.content_type
310
+ @file.content_type.to_s.chomp
311
+ end
315
312
  end
316
313
 
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
314
+ # Guess content type from its file extension. Limit what to be returned to prevent spoofing.
315
+ def guessed_safe_content_type
316
+ return unless path
322
317
 
323
- def mime_types_content_type
324
- ::MIME::Types.type_for(path).first.to_s if path
318
+ type = Marcel::Magic.by_path(original_filename).to_s
319
+ type if type.start_with?('text/') || type.start_with?('application/json')
325
320
  end
326
321
 
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
- ]
322
+ def identified_content_type
323
+ with_io do |io|
324
+ Marcel::Magic.by_magic(io).try(:type)
325
+ end
326
+ rescue Errno::ENOENT
327
+ nil
328
+ end
333
329
 
334
- extension_matchers.each do |regexp|
335
- if filename =~ regexp
336
- return $1, $2
330
+ def with_io(&block)
331
+ if file.is_a?(IO)
332
+ begin
333
+ yield file
334
+ ensure
335
+ file.try(:rewind)
337
336
  end
337
+ elsif path
338
+ File.open(path, &block)
338
339
  end
339
- return filename, "" # In case we weren't able to split the extension
340
340
  end
341
-
342
341
  end # SanitizedFile
343
342
  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
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
  module Storage
5
3
 
@@ -9,13 +7,17 @@ module CarrierWave
9
7
  # pretty much it.
10
8
  #
11
9
  class File < Abstract
10
+ def initialize(*)
11
+ super
12
+ @cache_called = nil
13
+ end
12
14
 
13
15
  ##
14
16
  # Move the file to the uploader's store path.
15
17
  #
16
18
  # By default, store!() uses copy_to(), which operates by copying the file
17
19
  # from the cache to the store, then deleting the file from the cache.
18
- # If move_to_store() is overriden to return true, then store!() uses move_to(),
20
+ # If move_to_store() is overridden to return true, then store!() uses move_to(),
19
21
  # which simply moves the file from cache to store. Useful for large files.
20
22
  #
21
23
  # === Parameters
@@ -51,6 +53,72 @@ module CarrierWave
51
53
  CarrierWave::SanitizedFile.new(path)
52
54
  end
53
55
 
56
+ ##
57
+ # Stores given file to cache directory.
58
+ #
59
+ # === Parameters
60
+ #
61
+ # [new_file (File, IOString, Tempfile)] any kind of file object
62
+ #
63
+ # === Returns
64
+ #
65
+ # [CarrierWave::SanitizedFile] a sanitized file
66
+ #
67
+ def cache!(new_file)
68
+ new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true)
69
+ rescue Errno::EMLINK, Errno::ENOSPC => e
70
+ raise(e) if @cache_called
71
+ @cache_called = true
72
+
73
+ # NOTE: Remove cached files older than 10 minutes
74
+ clean_cache!(600)
75
+
76
+ cache!(new_file)
77
+ end
78
+
79
+ ##
80
+ # Retrieves the file with the given cache_name from the cache.
81
+ #
82
+ # === Parameters
83
+ #
84
+ # [cache_name (String)] uniquely identifies a cache file
85
+ #
86
+ # === Raises
87
+ #
88
+ # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
89
+ #
90
+ def retrieve_from_cache!(identifier)
91
+ CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root))
92
+ end
93
+
94
+ ##
95
+ # Deletes a cache dir
96
+ #
97
+ def delete_dir!(path)
98
+ if path
99
+ begin
100
+ Dir.rmdir(::File.expand_path(path, uploader.root))
101
+ rescue Errno::ENOENT
102
+ # Ignore: path does not exist
103
+ rescue Errno::ENOTDIR
104
+ # Ignore: path is not a dir
105
+ rescue Errno::ENOTEMPTY, Errno::EEXIST
106
+ # Ignore: dir is not empty
107
+ end
108
+ end
109
+ end
110
+
111
+ def clean_cache!(seconds)
112
+ Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), uploader.root)).each do |dir|
113
+ # generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
114
+ matched = dir.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first
115
+ next unless matched
116
+ time = Time.at(matched[0].to_i)
117
+ if time < (Time.now.utc - seconds)
118
+ FileUtils.rm_rf(dir)
119
+ end
120
+ end
121
+ end
54
122
  end # File
55
123
  end # Storage
56
124
  end # CarrierWave