carrierwave 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -0,0 +1,284 @@
1
+ module CarrierWave
2
+
3
+ ##
4
+ # This module simplifies manipulation with vips by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'carrierwave/processing/vips'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < CarrierWave::Uploader::Base
13
+ # include CarrierWave::Vips
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < CarrierWave::Uploader::Base
19
+ # include CarrierWave::Vips
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful vips! method, which
25
+ # yields an ImageProcessing::Builder object. Check out the ImageProcessing
26
+ # docs at http://github.com/janko-m/image_processing and the list of all
27
+ # available Vips options at
28
+ # https://libvips.github.io/libvips/API/current/using-cli.html for more info.
29
+ #
30
+ # class MyUploader < CarrierWave::Uploader::Base
31
+ # include CarrierWave::Vips
32
+ #
33
+ # process :radial_blur => 10
34
+ #
35
+ # def radial_blur(amount)
36
+ # vips! do |builder|
37
+ # builder.radial_blur(amount)
38
+ # builder = yield(builder) if block_given?
39
+ # builder
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # === Note
45
+ #
46
+ # The ImageProcessing gem uses ruby-vips, a binding for the vips image
47
+ # library. You can find more information here:
48
+ #
49
+ # https://github.com/libvips/ruby-vips
50
+ #
51
+ #
52
+ module Vips
53
+ extend ActiveSupport::Concern
54
+
55
+ included do
56
+ require "image_processing/vips"
57
+ # We need to disable caching since we're editing images in place.
58
+ ::Vips.cache_set_max(0)
59
+ end
60
+
61
+ module ClassMethods
62
+ def convert(format)
63
+ process :convert => format
64
+ end
65
+
66
+ def resize_to_limit(width, height)
67
+ process :resize_to_limit => [width, height]
68
+ end
69
+
70
+ def resize_to_fit(width, height)
71
+ process :resize_to_fit => [width, height]
72
+ end
73
+
74
+ def resize_to_fill(width, height, gravity='centre')
75
+ process :resize_to_fill => [width, height, gravity]
76
+ end
77
+
78
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil)
79
+ process :resize_and_pad => [width, height, background, gravity, alpha]
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Changes the image encoding format to the given format
85
+ #
86
+ # See https://libvips.github.io/libvips/API/current/using-cli.html#using-command-line-conversion
87
+ #
88
+ # === Parameters
89
+ #
90
+ # [format (#to_s)] an abbreviation of the format
91
+ #
92
+ # === Yields
93
+ #
94
+ # [Vips::Image] additional manipulations to perform
95
+ #
96
+ # === Examples
97
+ #
98
+ # image.convert(:png)
99
+ #
100
+ def convert(format, page=nil)
101
+ vips! do |builder|
102
+ builder = builder.convert(format)
103
+ builder = builder.loader(page: page) if page
104
+ builder
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Resize the image to fit within the specified dimensions while retaining
110
+ # the original aspect ratio. Will only resize the image if it is larger than the
111
+ # specified dimensions. The resulting image may be shorter or narrower than specified
112
+ # in the smaller dimension but will not be larger than the specified values.
113
+ #
114
+ # === Parameters
115
+ #
116
+ # [width (Integer)] the width to scale the image to
117
+ # [height (Integer)] the height to scale the image to
118
+ # [combine_options (Hash)] additional Vips options to apply before resizing
119
+ #
120
+ # === Yields
121
+ #
122
+ # [Vips::Image] additional manipulations to perform
123
+ #
124
+ def resize_to_limit(width, height, combine_options: {})
125
+ width, height = resolve_dimensions(width, height)
126
+
127
+ vips! do |builder|
128
+ builder.resize_to_limit(width, height)
129
+ .apply(combine_options)
130
+ end
131
+ end
132
+
133
+ ##
134
+ # Resize the image to fit within the specified dimensions while retaining
135
+ # the original aspect ratio. The image may be shorter or narrower than
136
+ # specified in the smaller dimension but will not be larger than the specified values.
137
+ #
138
+ # === Parameters
139
+ #
140
+ # [width (Integer)] the width to scale the image to
141
+ # [height (Integer)] the height to scale the image to
142
+ # [combine_options (Hash)] additional Vips options to apply before resizing
143
+ #
144
+ # === Yields
145
+ #
146
+ # [Vips::Image] additional manipulations to perform
147
+ #
148
+ def resize_to_fit(width, height, combine_options: {})
149
+ width, height = resolve_dimensions(width, height)
150
+
151
+ vips! do |builder|
152
+ builder.resize_to_fit(width, height)
153
+ .apply(combine_options)
154
+ end
155
+ end
156
+
157
+ ##
158
+ # Resize the image to fit within the specified dimensions while retaining
159
+ # the aspect ratio of the original image. If necessary, crop the image in the
160
+ # larger dimension.
161
+ #
162
+ # === Parameters
163
+ #
164
+ # [width (Integer)] the width to scale the image to
165
+ # [height (Integer)] the height to scale the image to
166
+ # [combine_options (Hash)] additional vips options to apply before resizing
167
+ #
168
+ # === Yields
169
+ #
170
+ # [Vips::Image] additional manipulations to perform
171
+ #
172
+ def resize_to_fill(width, height, _gravity = nil, combine_options: {})
173
+ width, height = resolve_dimensions(width, height)
174
+
175
+ vips! do |builder|
176
+ builder.resize_to_fill(width, height).apply(combine_options)
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Resize the image to fit within the specified dimensions while retaining
182
+ # the original aspect ratio. If necessary, will pad the remaining area
183
+ # with the given color, which defaults to transparent (for gif and png,
184
+ # white for jpeg).
185
+ #
186
+ # See https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsCompassDirection
187
+ # for gravity options.
188
+ #
189
+ # === Parameters
190
+ #
191
+ # [width (Integer)] the width to scale the image to
192
+ # [height (Integer)] the height to scale the image to
193
+ # [background (List, nil)] the color of the background as a RGB, like [0, 255, 255], nil indicates transparent
194
+ # [gravity (String)] how to position the image
195
+ # [alpha (Boolean, nil)] pad the image with the alpha channel if supported
196
+ # [combine_options (Hash)] additional vips options to apply before resizing
197
+ #
198
+ # === Yields
199
+ #
200
+ # [Vips::Image] additional manipulations to perform
201
+ #
202
+ def resize_and_pad(width, height, background=nil, gravity='centre', alpha=nil, combine_options: {})
203
+ width, height = resolve_dimensions(width, height)
204
+
205
+ vips! do |builder|
206
+ builder.resize_and_pad(width, height, background: background, gravity: gravity, alpha: alpha)
207
+ .apply(combine_options)
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Returns the width of the image in pixels.
213
+ #
214
+ # === Returns
215
+ #
216
+ # [Integer] the image's width in pixels
217
+ #
218
+ def width
219
+ vips_image.width
220
+ end
221
+
222
+ ##
223
+ # Returns the height of the image in pixels.
224
+ #
225
+ # === Returns
226
+ #
227
+ # [Integer] the image's height in pixels
228
+ #
229
+ def height
230
+ vips_image.height
231
+ end
232
+
233
+ # Process the image with vip, using the ImageProcessing gem. This
234
+ # method will build a "convert" vips command and execute it on the
235
+ # current image.
236
+ #
237
+ # === Gotcha
238
+ #
239
+ # This method assumes that the object responds to +current_path+.
240
+ # Any class that this module is mixed into must have a +current_path+ method.
241
+ # CarrierWave::Uploader does, so you won't need to worry about this in
242
+ # most cases.
243
+ #
244
+ # === Yields
245
+ #
246
+ # [ImageProcessing::Builder] use it to define processing to be performed
247
+ #
248
+ # === Raises
249
+ #
250
+ # [CarrierWave::ProcessingError] if processing failed.
251
+ def vips!
252
+ builder = ImageProcessing::Vips.source(current_path)
253
+ builder = yield(builder)
254
+
255
+ result = builder.call
256
+ result.close
257
+
258
+ FileUtils.mv result.path, current_path
259
+
260
+ if File.extname(result.path) != File.extname(current_path)
261
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
262
+ file.content_type = ::MiniMime.lookup_by_filename(move_to).content_type
263
+ file.move_to(move_to, permissions, directory_permissions)
264
+ end
265
+ rescue ::Vips::Error => e
266
+ message = I18n.translate(:"errors.messages.vips_processing_error", :e => e)
267
+ raise CarrierWave::ProcessingError, message
268
+ end
269
+
270
+ private
271
+
272
+ def resolve_dimensions(*dimensions)
273
+ dimensions.map do |value|
274
+ next value unless value.instance_of?(Proc)
275
+ value.arity >= 1 ? value.call(self) : value.call
276
+ end
277
+ end
278
+
279
+ def vips_image
280
+ ::Vips::Image.new_from_buffer(read, "")
281
+ end
282
+
283
+ end # Vips
284
+ end # CarrierWave
@@ -2,6 +2,7 @@ require 'pathname'
2
2
  require 'active_support/core_ext/string/multibyte'
3
3
  require 'mini_mime'
4
4
  require 'mimemagic'
5
+ require 'mimemagic/overlay'
5
6
 
6
7
  module CarrierWave
7
8
 
@@ -181,9 +182,9 @@ module CarrierWave
181
182
  move!(new_path)
182
183
  chmod!(new_path, permissions)
183
184
  if keep_filename
184
- self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
185
+ self.file = {:tempfile => new_path, :filename => original_filename, :content_type => @content_type}
185
186
  else
186
- self.file = {:tempfile => new_path, :content_type => content_type}
187
+ self.file = {:tempfile => new_path, :content_type => @content_type}
187
188
  end
188
189
  self
189
190
  end
@@ -305,7 +306,7 @@ module CarrierWave
305
306
  def mkdir!(path, directory_permissions)
306
307
  options = {}
307
308
  options[:mode] = directory_permissions if directory_permissions
308
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exist?(File.dirname(path))
309
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
309
310
  end
310
311
 
311
312
  def chmod!(path, permissions)
@@ -330,9 +331,16 @@ module CarrierWave
330
331
 
331
332
  def mime_magic_content_type
332
333
  if path
333
- File.open(path) do |file|
334
- MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
334
+ type = File.open(path) do |file|
335
+ MimeMagic.by_magic(file).try(:type)
335
336
  end
337
+
338
+ if type.nil?
339
+ type = ::MiniMime.lookup_by_filename(path).try(:content_type)
340
+ type = 'invalid/invalid' unless type.nil? || type.start_with?('text/')
341
+ end
342
+
343
+ type
336
344
  end
337
345
  rescue Errno::ENOENT
338
346
  nil
@@ -161,6 +161,8 @@ module CarrierWave
161
161
  end
162
162
 
163
163
  class File
164
+ DEFAULT_S3_REGION = 'us-east-1'
165
+
164
166
  include CarrierWave::Utilities::Uri
165
167
 
166
168
  ##
@@ -194,11 +196,11 @@ module CarrierWave
194
196
  # [NilClass] no authenticated url available
195
197
  #
196
198
  def authenticated_url(options = {})
197
- if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun'].include?(@uploader.fog_credentials[:provider])
199
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(@uploader.fog_credentials[:provider])
198
200
  # avoid a get by using local references
199
201
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
200
202
  local_file = local_directory.files.new(:key => path)
201
- expire_at = options[:expire_at] || ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
203
+ expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
202
204
  case @uploader.fog_credentials[:provider]
203
205
  when 'AWS', 'Google'
204
206
  # Older versions of fog-google do not support options as a parameter
@@ -384,9 +386,15 @@ module CarrierWave
384
386
  if valid_subdomain
385
387
  s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
386
388
  "#{protocol}://#{@uploader.fog_directory}.#{s3_subdomain}.amazonaws.com/#{encoded_path}"
387
- else
388
- # directory is not a valid subdomain, so use path style for access
389
- "#{protocol}://s3.amazonaws.com/#{@uploader.fog_directory}/#{encoded_path}"
389
+ else # directory is not a valid subdomain, so use path style for access
390
+ region = @uploader.fog_credentials[:region].to_s
391
+ host = case region
392
+ when DEFAULT_S3_REGION, ''
393
+ 's3.amazonaws.com'
394
+ else
395
+ "s3.#{region}.amazonaws.com"
396
+ end
397
+ "#{protocol}://#{host}/#{@uploader.fog_directory}/#{encoded_path}"
390
398
  end
391
399
  end
392
400
  when 'Google'
@@ -442,7 +450,7 @@ module CarrierWave
442
450
  # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
443
451
  #
444
452
  def copy_to(new_path)
445
- connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
453
+ connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, copy_options)
446
454
  CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
447
455
  end
448
456
 
@@ -486,9 +494,18 @@ module CarrierWave
486
494
  @file ||= directory.files.head(path)
487
495
  end
488
496
 
497
+ def copy_options
498
+ options = {}
499
+ options.merge!(acl_header) if acl_header.present?
500
+ options['Content-Type'] ||= content_type if content_type
501
+ options.merge(@uploader.fog_attributes)
502
+ end
503
+
489
504
  def acl_header
490
505
  if fog_provider == 'AWS'
491
506
  { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
507
+ elsif fog_provider == "Google"
508
+ @uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
492
509
  else
493
510
  {}
494
511
  end
@@ -76,7 +76,7 @@ module CarrierWave
76
76
  # [Bool] whether the current file is cached
77
77
  #
78
78
  def cached?
79
- @cache_id
79
+ !!@cache_id
80
80
  end
81
81
 
82
82
  ##
@@ -8,39 +8,48 @@ module CarrierWave
8
8
  end
9
9
 
10
10
  ##
11
- # Override this method in your uploader to provide a blacklist of files content types
11
+ # Override this method in your uploader to provide a denylist of files content types
12
12
  # which are not allowed to be uploaded.
13
13
  # Not only strings but Regexp are allowed as well.
14
14
  #
15
15
  # === Returns
16
16
  #
17
- # [NilClass, String, Regexp, Array[String, Regexp]] a blacklist of content types which are not allowed to be uploaded
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] a denylist of content types which are not allowed to be uploaded
18
18
  #
19
19
  # === Examples
20
20
  #
21
- # def content_type_blacklist
21
+ # def content_type_denylist
22
22
  # %w(text/json application/json)
23
23
  # end
24
24
  #
25
25
  # Basically the same, but using a Regexp:
26
26
  #
27
- # def content_type_blacklist
27
+ # def content_type_denylist
28
28
  # [/(text|application)\/json/]
29
29
  # end
30
30
  #
31
- def content_type_blacklist; end
31
+ def content_type_denylist
32
+ if respond_to?(:content_type_blacklist)
33
+ ActiveSupport::Deprecation.warn "#content_type_blacklist is deprecated, use #content_type_denylist instead." unless instance_variable_defined?(:@content_type_blacklist_warned)
34
+ @content_type_blacklist_warned = true
35
+ content_type_blacklist
36
+ end
37
+ end
32
38
 
33
39
  private
34
40
 
35
41
  def check_content_type_blacklist!(new_file)
42
+ return unless content_type_denylist
43
+
36
44
  content_type = new_file.content_type
37
- if content_type_blacklist && blacklisted_content_type?(content_type)
38
- raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_blacklist_error", content_type: content_type)
45
+ if blacklisted_content_type?(content_type)
46
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_blacklist_error",
47
+ content_type: content_type, default: :"errors.messages.content_type_denylist_error")
39
48
  end
40
49
  end
41
50
 
42
51
  def blacklisted_content_type?(content_type)
43
- Array(content_type_blacklist).any? { |item| content_type =~ /#{item}/ }
52
+ Array(content_type_denylist).any? { |item| content_type =~ /#{item}/ }
44
53
  end
45
54
 
46
55
  end # ContentTypeBlacklist