carrierwave 0.9.0 → 3.0.2

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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +508 -158
  3. data/lib/carrierwave/compatibility/paperclip.rb +31 -21
  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 +220 -187
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +24 -34
  11. data/lib/carrierwave/processing/mini_magick.rb +142 -79
  12. data/lib/carrierwave/processing/rmagick.rb +76 -35
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +89 -70
  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 -58
  19. data/lib/carrierwave/storage.rb +1 -7
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +88 -44
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +81 -9
  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 +5 -69
  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 +54 -21
  34. data/lib/carrierwave/uploader/proxy.rb +30 -8
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +3 -5
  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 +173 -124
  40. data/lib/carrierwave/uploader.rb +12 -6
  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 +2 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -13
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +41 -16
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +224 -100
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/nl.yml +0 -11
  53. data/lib/carrierwave/locale/sk.yml +0 -11
  54. data/lib/carrierwave/processing/mime_types.rb +0 -73
  55. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  56. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'pathname'
4
2
  require 'active_support/core_ext/string/multibyte'
3
+ require 'marcel'
5
4
 
6
5
  module CarrierWave
7
6
 
@@ -14,19 +13,21 @@ module CarrierWave
14
13
  # It's probably needlessly comprehensive and complex. Help is appreciated.
15
14
  #
16
15
  class SanitizedFile
16
+ include CarrierWave::Utilities::FileName
17
17
 
18
- attr_accessor :file
18
+ attr_reader :file
19
19
 
20
20
  class << self
21
21
  attr_writer :sanitize_regexp
22
22
 
23
23
  def sanitize_regexp
24
- @sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
24
+ @sanitize_regexp ||= /[^[:word:]\.\-\+]/
25
25
  end
26
26
  end
27
27
 
28
28
  def initialize(file)
29
29
  self.file = file
30
+ @content = @content_type = nil
30
31
  end
31
32
 
32
33
  ##
@@ -38,7 +39,7 @@ module CarrierWave
38
39
  #
39
40
  def original_filename
40
41
  return @original_filename if @original_filename
41
- if @file and @file.respond_to?(:original_filename)
42
+ if @file && @file.respond_to?(:original_filename)
42
43
  @file.original_filename
43
44
  elsif path
44
45
  File.basename(path)
@@ -58,29 +59,6 @@ module CarrierWave
58
59
 
59
60
  alias_method :identifier, :filename
60
61
 
61
- ##
62
- # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
63
- # this would return 'test'
64
- #
65
- # === Returns
66
- #
67
- # [String] the first part of the filename
68
- #
69
- def basename
70
- split_extension(filename)[0] if filename
71
- end
72
-
73
- ##
74
- # Returns the file extension
75
- #
76
- # === Returns
77
- #
78
- # [String] the extension
79
- #
80
- def extension
81
- split_extension(filename)[1] if filename
82
- end
83
-
84
62
  ##
85
63
  # Returns the file's size.
86
64
  #
@@ -108,12 +86,11 @@ module CarrierWave
108
86
  # [String, nil] the path where the file is located.
109
87
  #
110
88
  def path
111
- unless @file.blank?
112
- if is_path?
113
- File.expand_path(@file)
114
- elsif @file.respond_to?(:path) and not @file.path.blank?
115
- File.expand_path(@file.path)
116
- 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)
117
94
  end
118
95
  end
119
96
 
@@ -132,7 +109,7 @@ module CarrierWave
132
109
  # [Boolean] whether the file is valid and has a non-zero size
133
110
  #
134
111
  def empty?
135
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
112
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
136
113
  end
137
114
 
138
115
  ##
@@ -141,8 +118,7 @@ module CarrierWave
141
118
  # [Boolean] Whether the file exists
142
119
  #
143
120
  def exists?
144
- return File.exists?(self.path) if self.path
145
- return false
121
+ self.path.present? && File.exist?(self.path)
146
122
  end
147
123
 
148
124
  ##
@@ -152,15 +128,21 @@ module CarrierWave
152
128
  #
153
129
  # [String] contents of the file
154
130
  #
155
- def read
131
+ def read(*args)
156
132
  if @content
157
- @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
158
140
  elsif is_path?
159
- File.open(@file, "rb") {|file| file.read}
141
+ File.open(@file, "rb") {|file| file.read(*args)}
160
142
  else
161
- @file.rewind if @file.respond_to?(:rewind)
162
- @content = @file.read
163
- @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?)
164
146
  @content
165
147
  end
166
148
  end
@@ -174,19 +156,26 @@ module CarrierWave
174
156
  # [permissions (Integer)] permissions to set on the file in its new location.
175
157
  # [directory_permissions (Integer)] permissions to set on created directories.
176
158
  #
177
- def move_to(new_path, permissions=nil, directory_permissions=nil)
159
+ def move_to(new_path, permissions=nil, directory_permissions=nil, keep_filename=false)
178
160
  return if self.empty?
179
161
  new_path = File.expand_path(new_path)
180
162
 
181
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)
182
174
  if exists?
183
- FileUtils.mv(path, new_path) unless new_path == path
175
+ FileUtils.mv(path, new_path) unless File.identical?(new_path, path)
184
176
  else
185
177
  File.open(new_path, "wb") { |f| f.write(read) }
186
178
  end
187
- chmod!(new_path, permissions)
188
- self.file = new_path
189
- self
190
179
  end
191
180
 
192
181
  ##
@@ -207,13 +196,20 @@ module CarrierWave
207
196
  new_path = File.expand_path(new_path)
208
197
 
209
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)
210
208
  if exists?
211
209
  FileUtils.cp(path, new_path) unless new_path == path
212
210
  else
213
211
  File.open(new_path, "wb") { |f| f.write(read) }
214
212
  end
215
- chmod!(new_path, permissions)
216
- self.class.new({:tempfile => new_path, :content_type => content_type})
217
213
  end
218
214
 
219
215
  ##
@@ -243,8 +239,11 @@ module CarrierWave
243
239
  # [String] the content type of the file
244
240
  #
245
241
  def content_type
246
- return @content_type if @content_type
247
- @file.content_type.to_s.chomp if @file.respond_to?(:content_type) and @file.content_type
242
+ @content_type ||=
243
+ identified_content_type ||
244
+ declared_content_type ||
245
+ guessed_safe_content_type ||
246
+ Marcel::MimeType::BINARY
248
247
  end
249
248
 
250
249
  ##
@@ -275,11 +274,11 @@ module CarrierWave
275
274
  if file.is_a?(Hash)
276
275
  @file = file["tempfile"] || file[:tempfile]
277
276
  @original_filename = file["filename"] || file[:filename]
278
- @content_type = file["content_type"] || file[:content_type]
277
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
279
278
  else
280
279
  @file = file
281
280
  @original_filename = nil
282
- @content_type = nil
281
+ @declared_content_type = nil
283
282
  end
284
283
  end
285
284
 
@@ -287,7 +286,7 @@ module CarrierWave
287
286
  def mkdir!(path, directory_permissions)
288
287
  options = {}
289
288
  options[:mode] = directory_permissions if directory_permissions
290
- 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))
291
290
  end
292
291
 
293
292
  def chmod!(path, permissions)
@@ -296,28 +295,48 @@ module CarrierWave
296
295
 
297
296
  # Sanitize the filename, to prevent hacking
298
297
  def sanitize(name)
299
- name = name.gsub("\\", "/") # work-around for IE
298
+ name = name.scrub
299
+ name = name.tr("\\", "/") # work-around for IE
300
300
  name = File.basename(name)
301
- name = name.gsub(sanitize_regexp,"_")
301
+ name = name.gsub(sanitize_regexp, "_")
302
302
  name = "_#{name}" if name =~ /\A\.+\z/
303
- name = "unnamed" if name.size == 0
304
- return name.mb_chars.to_s
303
+ name = "unnamed" if name.size.zero?
304
+ name.mb_chars.to_s
305
305
  end
306
306
 
307
- def split_extension(filename)
308
- # regular expressions to try for identifying extensions
309
- extension_matchers = [
310
- /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
311
- /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
312
- ]
313
-
314
- extension_matchers.each do |regexp|
315
- if filename =~ regexp
316
- return $1, $2
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
317
311
  end
312
+ end
313
+
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
317
+
318
+ type = Marcel::Magic.by_path(original_filename).to_s
319
+ type if type.start_with?('text/') || type.start_with?('application/json')
320
+ end
321
+
322
+ def identified_content_type
323
+ with_io do |io|
324
+ Marcel::Magic.by_magic(io).try(:type)
318
325
  end
319
- return filename, "" # In case we weren't able to split the extension
326
+ rescue Errno::ENOENT
327
+ nil
320
328
  end
321
329
 
330
+ def with_io(&block)
331
+ if file.is_a?(IO)
332
+ begin
333
+ yield file
334
+ ensure
335
+ file.try(:rewind)
336
+ end
337
+ elsif path
338
+ File.open(path, &block)
339
+ end
340
+ end
322
341
  end # SanitizedFile
323
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