carrierwave 1.3.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -91
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -2
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/locale/en.yml +9 -6
  7. data/lib/carrierwave/mount.rb +48 -61
  8. data/lib/carrierwave/mounter.rb +167 -77
  9. data/lib/carrierwave/orm/activerecord.rb +15 -55
  10. data/lib/carrierwave/processing/mini_magick.rb +108 -123
  11. data/lib/carrierwave/processing/rmagick.rb +11 -15
  12. data/lib/carrierwave/processing/vips.rb +284 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +60 -66
  15. data/lib/carrierwave/storage/abstract.rb +5 -5
  16. data/lib/carrierwave/storage/file.rb +6 -5
  17. data/lib/carrierwave/storage/fog.rb +101 -62
  18. data/lib/carrierwave/storage.rb +1 -0
  19. data/lib/carrierwave/test/matchers.rb +11 -7
  20. data/lib/carrierwave/uploader/cache.rb +40 -24
  21. data/lib/carrierwave/uploader/callbacks.rb +1 -1
  22. data/lib/carrierwave/uploader/configuration.rb +38 -19
  23. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  24. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  25. data/lib/carrierwave/uploader/dimension.rb +66 -0
  26. data/lib/carrierwave/uploader/download.rb +2 -123
  27. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  28. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  29. data/lib/carrierwave/uploader/file_size.rb +2 -2
  30. data/lib/carrierwave/uploader/mountable.rb +6 -0
  31. data/lib/carrierwave/uploader/processing.rb +42 -7
  32. data/lib/carrierwave/uploader/proxy.rb +17 -4
  33. data/lib/carrierwave/uploader/serialization.rb +1 -1
  34. data/lib/carrierwave/uploader/store.rb +47 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +153 -105
  37. data/lib/carrierwave/uploader.rb +10 -17
  38. data/lib/carrierwave/utilities/file_name.rb +47 -0
  39. data/lib/carrierwave/utilities/uri.rb +14 -11
  40. data/lib/carrierwave/utilities.rb +1 -0
  41. data/lib/carrierwave/validations/active_model.rb +7 -9
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/carrierwave.rb +13 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +2 -2
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +100 -33
  47. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  48. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  49. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -51
  50. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -52
@@ -1,12 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'active_support/core_ext/string/multibyte'
3
-
4
- begin
5
- # Use mime/types/columnar if available, for reduced memory usage
6
- require 'mime/types/columnar'
7
- rescue LoadError
8
- require 'mime/types'
9
- end
3
+ require 'marcel'
10
4
 
11
5
  module CarrierWave
12
6
 
@@ -19,6 +13,7 @@ module CarrierWave
19
13
  # It's probably needlessly comprehensive and complex. Help is appreciated.
20
14
  #
21
15
  class SanitizedFile
16
+ include CarrierWave::Utilities::FileName
22
17
 
23
18
  attr_reader :file
24
19
 
@@ -32,7 +27,7 @@ module CarrierWave
32
27
 
33
28
  def initialize(file)
34
29
  self.file = file
35
- @content = nil
30
+ @content = @content_type = nil
36
31
  end
37
32
 
38
33
  ##
@@ -44,7 +39,7 @@ module CarrierWave
44
39
  #
45
40
  def original_filename
46
41
  return @original_filename if @original_filename
47
- if @file and @file.respond_to?(:original_filename)
42
+ if @file && @file.respond_to?(:original_filename)
48
43
  @file.original_filename
49
44
  elsif path
50
45
  File.basename(path)
@@ -64,29 +59,6 @@ module CarrierWave
64
59
 
65
60
  alias_method :identifier, :filename
66
61
 
67
- ##
68
- # Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
69
- # this would return 'test'
70
- #
71
- # === Returns
72
- #
73
- # [String] the first part of the filename
74
- #
75
- def basename
76
- split_extension(filename)[0] if filename
77
- end
78
-
79
- ##
80
- # Returns the file extension
81
- #
82
- # === Returns
83
- #
84
- # [String] the extension
85
- #
86
- def extension
87
- split_extension(filename)[1] if filename
88
- end
89
-
90
62
  ##
91
63
  # Returns the file's size.
92
64
  #
@@ -137,7 +109,7 @@ module CarrierWave
137
109
  # [Boolean] whether the file is valid and has a non-zero size
138
110
  #
139
111
  def empty?
140
- @file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
112
+ @file.nil? || self.size.nil? || (self.size.zero? && !self.exists?)
141
113
  end
142
114
 
143
115
  ##
@@ -156,15 +128,21 @@ module CarrierWave
156
128
  #
157
129
  # [String] contents of the file
158
130
  #
159
- def read
131
+ def read(*args)
160
132
  if @content
161
- @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
162
140
  elsif is_path?
163
- File.open(@file, "rb") {|file| file.read}
141
+ File.open(@file, "rb") {|file| file.read(*args)}
164
142
  else
165
143
  @file.try(:rewind)
166
- @content = @file.read
167
- @file.try(:close) unless @file.try(:closed?)
144
+ @content = @file.read(*args)
145
+ @file.try(:close) unless @file.class.ancestors.include?(::StringIO) || @file.try(:closed?)
168
146
  @content
169
147
  end
170
148
  end
@@ -185,13 +163,10 @@ module CarrierWave
185
163
  mkdir!(new_path, directory_permissions)
186
164
  move!(new_path)
187
165
  chmod!(new_path, permissions)
188
- if keep_filename
189
- self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
190
- else
191
- self.file = {:tempfile => new_path, :content_type => content_type}
192
- end
166
+ self.file = {tempfile: new_path, filename: keep_filename ? original_filename : nil, content_type: declared_content_type}
193
167
  self
194
168
  end
169
+
195
170
  ##
196
171
  # Helper to move file to new path.
197
172
  #
@@ -223,7 +198,7 @@ module CarrierWave
223
198
  mkdir!(new_path, directory_permissions)
224
199
  copy!(new_path)
225
200
  chmod!(new_path, permissions)
226
- self.class.new({:tempfile => new_path, :content_type => content_type})
201
+ self.class.new({tempfile: new_path, content_type: declared_content_type})
227
202
  end
228
203
 
229
204
  ##
@@ -264,12 +239,11 @@ module CarrierWave
264
239
  # [String] the content type of the file
265
240
  #
266
241
  def content_type
267
- return @content_type if @content_type
268
- if @file.respond_to?(:content_type) and @file.content_type
269
- @content_type = @file.content_type.to_s.chomp
270
- elsif path
271
- @content_type = ::MIME::Types.type_for(path).first.to_s
272
- end
242
+ @content_type ||=
243
+ identified_content_type ||
244
+ declared_content_type ||
245
+ guessed_safe_content_type ||
246
+ Marcel::MimeType::BINARY
273
247
  end
274
248
 
275
249
  ##
@@ -300,11 +274,11 @@ module CarrierWave
300
274
  if file.is_a?(Hash)
301
275
  @file = file["tempfile"] || file[:tempfile]
302
276
  @original_filename = file["filename"] || file[:filename]
303
- @content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
277
+ @declared_content_type = file["content_type"] || file[:content_type] || file["type"] || file[:type]
304
278
  else
305
279
  @file = file
306
280
  @original_filename = nil
307
- @content_type = nil
281
+ @declared_content_type = nil
308
282
  end
309
283
  end
310
284
 
@@ -321,28 +295,48 @@ module CarrierWave
321
295
 
322
296
  # Sanitize the filename, to prevent hacking
323
297
  def sanitize(name)
298
+ name = name.scrub
324
299
  name = name.tr("\\", "/") # work-around for IE
325
300
  name = File.basename(name)
326
- name = name.gsub(sanitize_regexp,"_")
301
+ name = name.gsub(sanitize_regexp, "_")
327
302
  name = "_#{name}" if name =~ /\A\.+\z/
328
- name = "unnamed" if name.size == 0
329
- return name.mb_chars.to_s
303
+ name = "unnamed" if name.size.zero?
304
+ name.mb_chars.to_s
330
305
  end
331
306
 
332
- def split_extension(filename)
333
- # regular expressions to try for identifying extensions
334
- extension_matchers = [
335
- /\A(.+)\.(tar\.([glx]?z|bz2))\z/, # matches "something.tar.gz"
336
- /\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
337
- ]
338
-
339
- extension_matchers.each do |regexp|
340
- if filename =~ regexp
341
- 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
342
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)
343
325
  end
344
- return filename, "" # In case we weren't able to split the extension
326
+ rescue Errno::ENOENT
327
+ nil
345
328
  end
346
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
347
341
  end # SanitizedFile
348
342
  end # CarrierWave
@@ -14,7 +14,7 @@ module CarrierWave
14
14
  end
15
15
 
16
16
  def identifier
17
- uploader.filename
17
+ uploader.deduplicated_filename
18
18
  end
19
19
 
20
20
  def store!(file)
@@ -24,19 +24,19 @@ module CarrierWave
24
24
  end
25
25
 
26
26
  def cache!(new_file)
27
- raise NotImplementedError.new("Need to implement #cache! if you want to use #{self.class.name} as a cache storage.")
27
+ raise NotImplementedError, "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
28
28
  end
29
29
 
30
30
  def retrieve_from_cache!(identifier)
31
- raise NotImplementedError.new("Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage.")
31
+ raise NotImplementedError, "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
32
32
  end
33
33
 
34
34
  def delete_dir!(path)
35
- raise NotImplementedError.new("Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage.")
35
+ raise NotImplementedError, "Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage."
36
36
  end
37
37
 
38
38
  def clean_cache!(seconds)
39
- raise NotImplementedError.new("Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage.")
39
+ raise NotImplementedError, "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
40
40
  end
41
41
  end # Abstract
42
42
  end # Storage
@@ -17,7 +17,7 @@ module CarrierWave
17
17
  #
18
18
  # By default, store!() uses copy_to(), which operates by copying the file
19
19
  # from the cache to the store, then deleting the file from the cache.
20
- # 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(),
21
21
  # which simply moves the file from cache to store. Useful for large files.
22
22
  #
23
23
  # === Parameters
@@ -109,10 +109,11 @@ module CarrierWave
109
109
  end
110
110
 
111
111
  def clean_cache!(seconds)
112
- Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), CarrierWave.root)).each do |dir|
113
- # generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
114
- time = dir.scan(/(\d+)-\d+-\d+-\d+/).first.map(&:to_i)
115
- time = Time.at(*time)
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)
116
117
  if time < (Time.now.utc - seconds)
117
118
  FileUtils.rm_rf(dir)
118
119
  end
@@ -30,7 +30,7 @@ module CarrierWave
30
30
  #
31
31
  # Google credentials contain the following keys:
32
32
  # [:google_storage_access_key_id]
33
- # [:google_storage_secrete_access_key]
33
+ # [:google_storage_secret_access_key]
34
34
  #
35
35
  #
36
36
  # Local credentials contain the following keys:
@@ -60,6 +60,14 @@ module CarrierWave
60
60
  def connection_cache
61
61
  @connection_cache ||= {}
62
62
  end
63
+
64
+ def eager_load
65
+ # see #1198. This will hopefully no longer be necessary in future release of fog
66
+ fog_credentials = CarrierWave::Uploader::Base.fog_credentials
67
+ if fog_credentials.present?
68
+ CarrierWave::Storage::Fog.connection_cache[fog_credentials] ||= ::Fog::Storage.new(fog_credentials)
69
+ end
70
+ end
63
71
  end
64
72
 
65
73
  ##
@@ -138,9 +146,10 @@ module CarrierWave
138
146
  :key => uploader.fog_directory,
139
147
  :public => uploader.fog_public
140
148
  ).files.all(:prefix => uploader.cache_dir).each do |file|
141
- # generate_cache_id returns key formated TIMEINT-PID-COUNTER-RND
142
- time = file.key.scan(/(\d+)-\d+-\d+-\d+/).first.map { |t| t.to_i }
143
- time = Time.at(*time)
149
+ # generate_cache_id returns key formatted TIMEINT-PID(-COUNTER)-RND
150
+ matched = file.key.match(/(\d+)-\d+-\d+(?:-\d+)?/)
151
+ next unless matched
152
+ time = Time.at(matched[1].to_i)
144
153
  file.destroy if time < (Time.now.utc - seconds)
145
154
  end
146
155
  end
@@ -153,7 +162,10 @@ module CarrierWave
153
162
  end
154
163
 
155
164
  class File
165
+ DEFAULT_S3_REGION = 'us-east-1'.freeze
166
+
156
167
  include CarrierWave::Utilities::Uri
168
+ include CarrierWave::Utilities::FileName
157
169
 
158
170
  ##
159
171
  # Current local path to file
@@ -177,7 +189,7 @@ module CarrierWave
177
189
 
178
190
  ##
179
191
  # Return a temporary authenticated url to a private file, if available
180
- # Only supported for AWS, Rackspace, Google and AzureRM providers
192
+ # Only supported for AWS, Rackspace, Google, AzureRM and Aliyun providers
181
193
  #
182
194
  # === Returns
183
195
  #
@@ -186,24 +198,27 @@ module CarrierWave
186
198
  # [NilClass] no authenticated url available
187
199
  #
188
200
  def authenticated_url(options = {})
189
- if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM'].include?(@uploader.fog_credentials[:provider])
201
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(fog_provider)
190
202
  # avoid a get by using local references
191
203
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
192
204
  local_file = local_directory.files.new(:key => path)
193
- expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
194
- case @uploader.fog_credentials[:provider]
195
- when 'AWS', 'Google'
196
- # Older versions of fog-google do not support options as a parameter
197
- if url_options_supported?(local_file)
198
- local_file.url(expire_at, options)
199
- else
200
- warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
201
- local_file.url(expire_at)
202
- end
203
- when 'Rackspace', 'OpenStack'
204
- connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
205
+ expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
206
+ case fog_provider
207
+ when 'AWS', 'Google'
208
+ # Older versions of fog-google do not support options as a parameter
209
+ if url_options_supported?(local_file)
210
+ local_file.url(expire_at, options)
205
211
  else
212
+ warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider."
206
213
  local_file.url(expire_at)
214
+ end
215
+ when 'Rackspace', 'OpenStack'
216
+ connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
217
+ when 'Aliyun'
218
+ expire_at -= Time.now
219
+ local_file.url(expire_at)
220
+ else
221
+ local_file.url(expire_at)
207
222
  end
208
223
  end
209
224
  end
@@ -216,7 +231,7 @@ module CarrierWave
216
231
  # [String] value of content-type
217
232
  #
218
233
  def content_type
219
- @content_type || !file.nil? && file.content_type
234
+ @content_type || file.try(:content_type)
220
235
  end
221
236
 
222
237
  ##
@@ -239,19 +254,9 @@ module CarrierWave
239
254
  #
240
255
  def delete
241
256
  # avoid a get by just using local reference
242
- directory.files.new(:key => path).destroy
243
- end
244
-
245
- ##
246
- # Return extension of file
247
- #
248
- # === Returns
249
- #
250
- # [String] extension of file or nil if the file has no extension
251
- #
252
- def extension
253
- path_elements = path.split('.')
254
- path_elements.last if path_elements.size > 1
257
+ directory.files.new(:key => path).destroy.tap do |result|
258
+ @file = nil if result
259
+ end
255
260
  end
256
261
 
257
262
  ##
@@ -280,16 +285,16 @@ module CarrierWave
280
285
  #
281
286
  # [String] contents of file
282
287
  def read
283
- file_body = file.body
288
+ file_body = file&.body
284
289
 
285
290
  return if file_body.nil?
286
291
  return file_body unless file_body.is_a?(::File)
287
292
 
288
- # Fog::Storage::XXX::File#body could return the source file which was upoloaded to the remote server.
289
- read_source_file(file_body) if ::File.exist?(file_body.path)
293
+ # Fog::Storage::XXX::File#body could return the source file which was uploaded to the remote server.
294
+ return read_source_file if ::File.exist?(file_body.path)
290
295
 
291
296
  # If the source file doesn't exist, the remote content is read
292
- @file = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
297
+ @file = nil
293
298
  file.body
294
299
  end
295
300
 
@@ -327,7 +332,7 @@ module CarrierWave
327
332
  fog_file = new_file.to_file
328
333
  @content_type ||= new_file.content_type
329
334
  @file = directory.files.create({
330
- :body => fog_file ? fog_file : new_file.read,
335
+ :body => fog_file || new_file.read,
331
336
  :content_type => @content_type,
332
337
  :key => path,
333
338
  :public => @uploader.fog_public
@@ -348,7 +353,7 @@ module CarrierWave
348
353
  #
349
354
  def public_url
350
355
  encoded_path = encode_path(path)
351
- if host = @uploader.asset_host
356
+ if (host = @uploader.asset_host)
352
357
  if host.respond_to? :call
353
358
  "#{host.call(self)}/#{encoded_path}"
354
359
  else
@@ -365,15 +370,22 @@ module CarrierWave
365
370
  protocol = @uploader.fog_use_ssl_for_aws ? "https" : "http"
366
371
 
367
372
  subdomain_regex = /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9\.]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
368
- valid_subdomain = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
369
-
370
- # if directory is a valid subdomain, use that style for access
371
- if valid_subdomain
372
- s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
373
- "#{protocol}://#{@uploader.fog_directory}.#{s3_subdomain}.amazonaws.com/#{encoded_path}"
374
- else
375
- # directory is not a valid subdomain, so use path style for access
376
- "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}"
373
+ # To use the virtual-hosted style, the bucket name needs to be representable as a subdomain
374
+ use_virtual_hosted_style = @uploader.fog_directory.to_s =~ subdomain_regex && !(protocol == 'https' && @uploader.fog_directory =~ /\./)
375
+
376
+ region = @uploader.fog_credentials[:region].to_s
377
+ regional_host = case region
378
+ when DEFAULT_S3_REGION, ''
379
+ 's3.amazonaws.com'
380
+ else
381
+ "s3.#{region}.amazonaws.com"
382
+ end
383
+
384
+ if use_virtual_hosted_style
385
+ regional_host = 's3-accelerate.amazonaws.com' if @uploader.fog_aws_accelerate
386
+ "#{protocol}://#{@uploader.fog_directory}.#{regional_host}/#{encoded_path}"
387
+ else # directory is not a valid subdomain, so use path style for access
388
+ "#{protocol}://#{regional_host}/#{@uploader.fog_directory}/#{encoded_path}"
377
389
  end
378
390
  end
379
391
  when 'Google'
@@ -387,7 +399,7 @@ module CarrierWave
387
399
  end
388
400
 
389
401
  ##
390
- # Return url to file, if avaliable
402
+ # Return url to file, if available
391
403
  #
392
404
  # === Returns
393
405
  #
@@ -413,7 +425,7 @@ module CarrierWave
413
425
  # [NilClass] no file name available
414
426
  #
415
427
  def filename(options = {})
416
- return unless file_url = url(options)
428
+ return unless (file_url = url(options))
417
429
  CGI.unescape(file_url.split('?').first).gsub(/.*\/(.*?$)/, '\1')
418
430
  end
419
431
 
@@ -429,10 +441,29 @@ module CarrierWave
429
441
  # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
430
442
  #
431
443
  def copy_to(new_path)
432
- connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
444
+ file.copy(@uploader.fog_directory, new_path, copy_options)
433
445
  CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
434
446
  end
435
447
 
448
+ ##
449
+ # Return the local file
450
+ #
451
+ # === Returns
452
+ #
453
+ # [File] The local file as Ruby's File class
454
+ # or
455
+ # [NilClass] When there's no file, or the file is remotely stored
456
+ #
457
+ def to_file
458
+ return nil unless file.body.is_a? ::File
459
+
460
+ if file.body.closed?
461
+ ::File.open(file.body.path) # Reopen if it's already closed
462
+ else
463
+ file.body
464
+ end
465
+ end
466
+
436
467
  private
437
468
 
438
469
  ##
@@ -454,12 +485,10 @@ module CarrierWave
454
485
  # [Fog::#{provider}::Directory] containing directory
455
486
  #
456
487
  def directory
457
- @directory ||= begin
458
- connection.directories.new(
459
- :key => @uploader.fog_directory,
460
- :public => @uploader.fog_public
461
- )
462
- end
488
+ @directory ||= connection.directories.new(
489
+ :key => @uploader.fog_directory,
490
+ :public => @uploader.fog_public
491
+ )
463
492
  end
464
493
 
465
494
  ##
@@ -473,9 +502,19 @@ module CarrierWave
473
502
  @file ||= directory.files.head(path)
474
503
  end
475
504
 
505
+ def copy_options
506
+ options = {}
507
+ options.merge!(acl_header) if acl_header.present?
508
+ options[fog_provider == "Google" ? :content_type : 'Content-Type'] ||= content_type if content_type
509
+ options.merge(@uploader.fog_attributes)
510
+ end
511
+
476
512
  def acl_header
477
- if fog_provider == 'AWS'
513
+ case fog_provider
514
+ when 'AWS'
478
515
  { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
516
+ when "Google"
517
+ @uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
479
518
  else
480
519
  {}
481
520
  end
@@ -485,14 +524,14 @@ module CarrierWave
485
524
  @uploader.fog_credentials[:provider].to_s
486
525
  end
487
526
 
488
- def read_source_file(file_body)
489
- return unless ::File.exist?(file_body.path)
527
+ def read_source_file
528
+ source_file = to_file
529
+ return unless source_file
490
530
 
491
531
  begin
492
- file_body = ::File.open(file_body.path) if file_body.closed? # Reopen if it's already closed
493
- file_body.read
532
+ source_file.read
494
533
  ensure
495
- file_body.close
534
+ source_file.close
496
535
  end
497
536
  end
498
537
 
@@ -1,2 +1,3 @@
1
1
  require "carrierwave/storage/abstract"
2
2
  require "carrierwave/storage/file"
3
+ require "carrierwave/storage/fog"
@@ -45,11 +45,11 @@ module CarrierWave
45
45
  def matches?(actual)
46
46
  @actual = actual
47
47
  # Satisfy expectation here. Return false or raise an error if it's not met.
48
- (File.stat(@actual.path).mode & 0777) == @expected
48
+ (File.stat(@actual.path).mode & 0o777) == @expected
49
49
  end
50
50
 
51
51
  def failure_message
52
- "expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
52
+ "expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0o777).to_s(8)}"
53
53
  end
54
54
 
55
55
  def failure_message_when_negated
@@ -76,11 +76,11 @@ module CarrierWave
76
76
  def matches?(actual)
77
77
  @actual = actual
78
78
  # Satisfy expectation here. Return false or raise an error if it's not met.
79
- (File.stat(File.dirname @actual.path).mode & 0777) == @expected
79
+ (File.stat(File.dirname(@actual.path)).mode & 0o777) == @expected
80
80
  end
81
81
 
82
82
  def failure_message
83
- "expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
83
+ "expected #{File.dirname @actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0o777).to_s(8)}"
84
84
  end
85
85
 
86
86
  def failure_message_when_negated
@@ -341,9 +341,11 @@ module CarrierWave
341
341
  begin
342
342
  require 'rmagick'
343
343
  rescue LoadError
344
- require 'RMagick'
345
- rescue LoadError
346
- puts "WARNING: Failed to require rmagick, image processing may fail!"
344
+ begin
345
+ require 'RMagick'
346
+ rescue LoadError
347
+ puts "WARNING: Failed to require rmagick, image processing may fail!"
348
+ end
347
349
  end
348
350
  end
349
351
  MagickWrapper.new(filename)
@@ -353,6 +355,7 @@ module CarrierWave
353
355
 
354
356
  class MagickWrapper # :nodoc:
355
357
  attr_reader :image
358
+
356
359
  def width
357
360
  image.columns
358
361
  end
@@ -372,6 +375,7 @@ module CarrierWave
372
375
 
373
376
  class MiniMagickWrapper # :nodoc:
374
377
  attr_reader :image
378
+
375
379
  def width
376
380
  image[:width]
377
381
  end