carrierwave 0.11.2 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
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 +217 -182
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +29 -35
  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 +171 -123
  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} +6 -10
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +135 -83
  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
+ Marcel::MimeType.for(declared_type: @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