carrierwave 1.3.2 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
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 +53 -61
  8. data/lib/carrierwave/mounter.rb +167 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  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 +157 -109
  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} +3 -3
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +103 -36
  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
+ Marcel::MimeType.for(declared_type: @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