carrierwave 2.0.1 → 2.2.1

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.

@@ -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
@@ -1,7 +1,7 @@
1
1
  require 'pathname'
2
2
  require 'active_support/core_ext/string/multibyte'
3
3
  require 'mini_mime'
4
- require 'mimemagic'
4
+ require 'marcel'
5
5
 
6
6
  module CarrierWave
7
7
 
@@ -181,9 +181,9 @@ module CarrierWave
181
181
  move!(new_path)
182
182
  chmod!(new_path, permissions)
183
183
  if keep_filename
184
- self.file = {:tempfile => new_path, :filename => original_filename, :content_type => content_type}
184
+ self.file = {:tempfile => new_path, :filename => original_filename, :content_type => @content_type}
185
185
  else
186
- self.file = {:tempfile => new_path, :content_type => content_type}
186
+ self.file = {:tempfile => new_path, :content_type => @content_type}
187
187
  end
188
188
  self
189
189
  end
@@ -261,7 +261,7 @@ module CarrierWave
261
261
  def content_type
262
262
  @content_type ||=
263
263
  existing_content_type ||
264
- mime_magic_content_type ||
264
+ marcel_magic_content_type ||
265
265
  mini_mime_content_type
266
266
  end
267
267
 
@@ -305,7 +305,7 @@ module CarrierWave
305
305
  def mkdir!(path, directory_permissions)
306
306
  options = {}
307
307
  options[:mode] = directory_permissions if directory_permissions
308
- FileUtils.mkdir_p(File.dirname(path), options) unless File.exist?(File.dirname(path))
308
+ FileUtils.mkdir_p(File.dirname(path), **options) unless File.exist?(File.dirname(path))
309
309
  end
310
310
 
311
311
  def chmod!(path, permissions)
@@ -328,11 +328,18 @@ module CarrierWave
328
328
  end
329
329
  end
330
330
 
331
- def mime_magic_content_type
331
+ def marcel_magic_content_type
332
332
  if path
333
- File.open(path) do |file|
334
- MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
333
+ type = File.open(path) do |file|
334
+ Marcel::Magic.by_magic(file).try(:type)
335
335
  end
336
+
337
+ if type.nil?
338
+ type = Marcel::Magic.by_path(file).try(:type)
339
+ type = 'invalid/invalid' unless type.nil? || type.start_with?('text/')
340
+ end
341
+
342
+ type
336
343
  end
337
344
  rescue Errno::ENOENT
338
345
  nil
@@ -147,8 +147,9 @@ module CarrierWave
147
147
  :public => uploader.fog_public
148
148
  ).files.all(:prefix => uploader.cache_dir).each do |file|
149
149
  # generate_cache_id returns key formated TIMEINT-PID(-COUNTER)-RND
150
- time = file.key.scan(/(\d+)-\d+-\d+(?:-\d+)?/).first.map { |t| t.to_i }
151
- time = Time.at(*time)
150
+ matched = file.key.match(/(\d+)-\d+-\d+(?:-\d+)?/)
151
+ next unless matched
152
+ time = Time.at(matched[1].to_i)
152
153
  file.destroy if time < (Time.now.utc - seconds)
153
154
  end
154
155
  end
@@ -161,6 +162,8 @@ module CarrierWave
161
162
  end
162
163
 
163
164
  class File
165
+ DEFAULT_S3_REGION = 'us-east-1'
166
+
164
167
  include CarrierWave::Utilities::Uri
165
168
 
166
169
  ##
@@ -194,11 +197,11 @@ module CarrierWave
194
197
  # [NilClass] no authenticated url available
195
198
  #
196
199
  def authenticated_url(options = {})
197
- if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun'].include?(@uploader.fog_credentials[:provider])
200
+ if ['AWS', 'Google', 'Rackspace', 'OpenStack', 'AzureRM', 'Aliyun', 'backblaze'].include?(@uploader.fog_credentials[:provider])
198
201
  # avoid a get by using local references
199
202
  local_directory = connection.directories.new(:key => @uploader.fog_directory)
200
203
  local_file = local_directory.files.new(:key => path)
201
- expire_at = options[:expire_at] || ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
204
+ expire_at = options[:expire_at] || ::Fog::Time.now.since(@uploader.fog_authenticated_url_expiration.to_i)
202
205
  case @uploader.fog_credentials[:provider]
203
206
  when 'AWS', 'Google'
204
207
  # Older versions of fog-google do not support options as a parameter
@@ -384,9 +387,15 @@ module CarrierWave
384
387
  if valid_subdomain
385
388
  s3_subdomain = @uploader.fog_aws_accelerate ? "s3-accelerate" : "s3"
386
389
  "#{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}"
390
+ else # directory is not a valid subdomain, so use path style for access
391
+ region = @uploader.fog_credentials[:region].to_s
392
+ host = case region
393
+ when DEFAULT_S3_REGION, ''
394
+ 's3.amazonaws.com'
395
+ else
396
+ "s3.#{region}.amazonaws.com"
397
+ end
398
+ "#{protocol}://#{host}/#{@uploader.fog_directory}/#{encoded_path}"
390
399
  end
391
400
  end
392
401
  when 'Google'
@@ -442,7 +451,7 @@ module CarrierWave
442
451
  # @return [CarrierWave::Storage::Fog::File] the location where the file will be stored.
443
452
  #
444
453
  def copy_to(new_path)
445
- connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, acl_header)
454
+ connection.copy_object(@uploader.fog_directory, file.key, @uploader.fog_directory, new_path, copy_options)
446
455
  CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
447
456
  end
448
457
 
@@ -486,9 +495,18 @@ module CarrierWave
486
495
  @file ||= directory.files.head(path)
487
496
  end
488
497
 
498
+ def copy_options
499
+ options = {}
500
+ options.merge!(acl_header) if acl_header.present?
501
+ options['Content-Type'] ||= content_type if content_type
502
+ options.merge(@uploader.fog_attributes)
503
+ end
504
+
489
505
  def acl_header
490
506
  if fog_provider == 'AWS'
491
507
  { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' }
508
+ elsif fog_provider == "Google"
509
+ @uploader.fog_public ? { destination_predefined_acl: "publicRead" } : {}
492
510
  else
493
511
  {}
494
512
  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