carrierwave 2.0.0 → 2.2.0

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85a5c93a317b317dcf6bf2a6f6ae520ddc9a6788df8552aa7339b85e2e86f8ba
4
- data.tar.gz: d8eae4edcbbbabda5ad0ae70d5e36a425677a34dc2e1b014ca4f3641ba2914c4
3
+ metadata.gz: a6e511db9d3eebc43bd42613884cf42ba2ad5f236a438a609cccc67c6ce3d192
4
+ data.tar.gz: 20cef424394b5a40d27e73e8677161870c6d35f60734fd05cf2d0666f461b834
5
5
  SHA512:
6
- metadata.gz: 0f6a3f1bbbc1aa7137383756b45d2d337254253932b70f2f1cf68440c7bcb37e2607650e58d38174d6487f31f1d7a0ddce1736971396e6107101193d5e84049a
7
- data.tar.gz: b2b6bc7e114fcc5f2194cbff2e03048ea669e9cfea1631f36bcf196566f70580da8d2fcc82c4a92b8497b1841073ae19629a61f8635573ceb32bb0430586bab8
6
+ metadata.gz: 28aeace46926db2716ac4600a67cb3a318c553f8f04f533fcf19afaddbafc46489bc3df5d523bc878f5b77164d4110c69ddfe346944caeb46b0dd83df39d7ac9
7
+ data.tar.gz: dc271f6fdfd5a515295185265a53b0b894ecef7e1d72bdcaa062357dacd5c7e9bca54a818ffcd344a95b341628451f5b6e579b62587845148075f9db9de949f7
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  This gem provides a simple and extremely flexible way to upload files from Ruby applications.
4
4
  It works well with Rack based web applications, such as Ruby on Rails.
5
5
 
6
- [![Build Status](https://travis-ci.org/carrierwaveuploader/carrierwave.svg?branch=master)](http://travis-ci.org/carrierwaveuploader/carrierwave)
6
+ [![Build Status](https://github.com/carrierwaveuploader/carrierwave/workflows/Test/badge.svg)](https://github.com/carrierwaveuploader/carrierwave/actions)
7
7
  [![Code Climate](https://codeclimate.com/github/carrierwaveuploader/carrierwave.svg)](https://codeclimate.com/github/carrierwaveuploader/carrierwave)
8
8
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=carrierwave&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=carrierwave&package-manager=bundler&version-scheme=semver)
9
9
 
@@ -94,7 +94,7 @@ a migration:
94
94
  Open your model file and mount the uploader:
95
95
 
96
96
  ```ruby
97
- class User < ActiveRecord::Base
97
+ class User < ApplicationRecord
98
98
  mount_uploader :avatar, AvatarUploader
99
99
  end
100
100
  ```
@@ -157,7 +157,7 @@ Open your model file and mount the uploader:
157
157
 
158
158
 
159
159
  ```ruby
160
- class User < ActiveRecord::Base
160
+ class User < ApplicationRecord
161
161
  mount_uploaders :avatars, AvatarUploader
162
162
  serialize :avatars, JSON # If you use SQLite, add this line.
163
163
  end
@@ -230,7 +230,7 @@ end
230
230
  ## Securing uploads
231
231
 
232
232
  Certain files might be dangerous if uploaded to the wrong location, such as PHP
233
- files or other script files. CarrierWave allows you to specify a whitelist of
233
+ files or other script files. CarrierWave allows you to specify an allowlist of
234
234
  allowed extensions or content types.
235
235
 
236
236
  If you're mounting the uploader, uploading a file with the wrong extension will
@@ -238,7 +238,7 @@ make the record invalid instead. Otherwise, an error is raised.
238
238
 
239
239
  ```ruby
240
240
  class MyUploader < CarrierWave::Uploader::Base
241
- def extension_whitelist
241
+ def extension_allowlist
242
242
  %w(jpg jpeg gif png)
243
243
  end
244
244
  end
@@ -249,45 +249,45 @@ Let's say we need an uploader that accepts only images. This can be done like th
249
249
 
250
250
  ```ruby
251
251
  class MyUploader < CarrierWave::Uploader::Base
252
- def content_type_whitelist
252
+ def content_type_allowlist
253
253
  /image\//
254
254
  end
255
255
  end
256
256
  ```
257
257
 
258
- You can use a blacklist to reject content types.
258
+ You can use a denylist to reject content types.
259
259
  Let's say we need an uploader that reject JSON files. This can be done like this
260
260
 
261
261
  ```ruby
262
262
  class NoJsonUploader < CarrierWave::Uploader::Base
263
- def content_type_blacklist
263
+ def content_type_denylist
264
264
  ['application/text', 'application/json']
265
265
  end
266
266
  end
267
267
  ```
268
268
 
269
269
  ### CVE-2016-3714 (ImageTragick)
270
- This version of CarrierWave has the ability to mitigate CVE-2016-3714. However, you **MUST** set a content_type_whitelist in your uploaders for this protection to be effective, and you **MUST** either disable ImageMagick's default SVG delegate or use the RSVG delegate for SVG processing.
270
+ This version of CarrierWave has the ability to mitigate CVE-2016-3714. However, you **MUST** set a content_type_allowlist in your uploaders for this protection to be effective, and you **MUST** either disable ImageMagick's default SVG delegate or use the RSVG delegate for SVG processing.
271
271
 
272
272
 
273
- A valid whitelist that will restrict your uploader to images only, and mitigate the CVE is:
273
+ A valid allowlist that will restrict your uploader to images only, and mitigate the CVE is:
274
274
 
275
275
  ```ruby
276
276
  class MyUploader < CarrierWave::Uploader::Base
277
- def content_type_whitelist
277
+ def content_type_allowlist
278
278
  [/image\//]
279
279
  end
280
280
  end
281
281
  ```
282
282
 
283
- **WARNING**: A `content_type_whitelist` is the only form of whitelist or blacklist supported by CarrierWave that can effectively mitigate against CVE-2016-3714. Use of `extension_whitelist` will not inspect the file headers, and thus still leaves your application open to the vulnerability.
283
+ **WARNING**: A `content_type_allowlist` is the only form of allowlist or denylist supported by CarrierWave that can effectively mitigate against CVE-2016-3714. Use of `extension_allowlist` will not inspect the file headers, and thus still leaves your application open to the vulnerability.
284
284
 
285
285
  ### Filenames and unicode chars
286
286
 
287
287
  Another security issue you should care for is the file names (see
288
288
  [Ruby On Rails Security Guide](http://guides.rubyonrails.org/security.html#file-uploads)).
289
289
  By default, CarrierWave provides only English letters, arabic numerals and some symbols as
290
- white-listed characters in the file name. If you want to support local scripts (Cyrillic letters, letters with diacritics and so on), you
290
+ allowlisted characters in the file name. If you want to support local scripts (Cyrillic letters, letters with diacritics and so on), you
291
291
  have to override `sanitize_regexp` method. It should return regular expression which would match
292
292
  all *non*-allowed symbols.
293
293
 
@@ -332,15 +332,13 @@ end
332
332
 
333
333
  When this uploader is used, an uploaded image would be scaled to be no larger
334
334
  than 800 by 800 pixels. The original aspect ratio will be kept.
335
- A version called thumb is then created, which is scaled
336
- to exactly 200 by 200 pixels.
337
335
 
338
- If you would like to crop images to a specific height and width you
339
- can use the alternative option of '''resize_to_fill'''. It will make sure
336
+ A version called `:thumb` is then created, which is scaled
337
+ to exactly 200 by 200 pixels. The thumbnail uses `resize_to_fill` which makes sure
340
338
  that the width and height specified are filled, only cropping
341
339
  if the aspect ratio requires it.
342
340
 
343
- The uploader could be used like this:
341
+ The above uploader could be used like this:
344
342
 
345
343
  ```ruby
346
344
  uploader = AvatarUploader.new
@@ -353,6 +351,18 @@ uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200
353
351
  One important thing to remember is that process is called *before* versions are
354
352
  created. This can cut down on processing cost.
355
353
 
354
+ ### Processing Methods: mini_magick
355
+
356
+ - `convert` - Changes the image encoding format to the given format, eg. jpg
357
+ - `resize_to_limit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. Will only resize the image if it is larger than the specified dimensions. The resulting image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
358
+ - `resize_to_fit` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. The image may be shorter or narrower than specified in the smaller dimension but will not be larger than the specified values.
359
+ - `resize_to_fill` - Resize the image to fit within the specified dimensions while retaining the aspect ratio of the original image. If necessary, crop the image in the larger dimension. Optionally, a "gravity" may be specified, for example "Center", or "NorthEast".
360
+ - `resize_and_pad` - Resize the image to fit within the specified dimensions while retaining the original aspect ratio. If necessary, will pad the remaining area with the given color, which defaults to transparent (for gif and png, white for jpeg). Optionally, a "gravity" may be specified, as above.
361
+
362
+ See `carrierwave/processing/mini_magick.rb` for details.
363
+
364
+ ### Nested versions
365
+
356
366
  It is possible to nest versions within versions:
357
367
 
358
368
  ```ruby
@@ -707,6 +717,9 @@ CarrierWave.configure do |config|
707
717
  config.fog_directory = 'name_of_bucket' # required
708
718
  config.fog_public = false # optional, defaults to true
709
719
  config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
720
+ # For an application which utilizes multiple servers but does not need caches persisted across requests,
721
+ # uncomment the line :file instead of the default :storage. Otherwise, it will use AWS as the temp cache store.
722
+ # config.cache_storage = :file
710
723
  end
711
724
  ```
712
725
 
@@ -787,30 +800,43 @@ end
787
800
  That's it! You can still use the `CarrierWave::Uploader#url` method to return
788
801
  the url to the file on Rackspace Cloud Files.
789
802
 
790
- ## Using Google Storage for Developers
803
+ ## Using Google Cloud Storage
791
804
 
792
- [Fog](http://github.com/fog/fog-google) is used to support Google Storage for Developers. Ensure you have it in your Gemfile:
805
+ [Fog](http://github.com/fog/fog-google) is used to support Google Cloud Storage. Ensure you have it in your Gemfile:
793
806
 
794
807
  ```ruby
795
808
  gem "fog-google"
796
- gem "google-api-client", "> 0.8.5", "< 0.9"
797
- gem "mime-types"
798
809
  ```
799
810
 
800
- You'll need to configure a directory (also known as a bucket), access key id and secret access key in the initializer.
811
+ You'll need to configure a directory (also known as a bucket) and the credentials in the initializer.
801
812
  For the sake of performance it is assumed that the directory already exists, so please create it if need be.
802
813
 
803
814
  Please read the [fog-google README](https://github.com/fog/fog-google/blob/master/README.md) on how to get credentials.
804
815
 
816
+ For Google Storage JSON API (recommended):
817
+ ```ruby
818
+ CarrierWave.configure do |config|
819
+ config.fog_provider = 'fog/google'
820
+ config.fog_credentials = {
821
+ provider: 'Google',
822
+ google_project: 'my-project',
823
+ google_json_key_string: 'xxxxxx'
824
+ # or use google_json_key_location if using an actual file
825
+ }
826
+ config.fog_directory = 'google_cloud_storage_bucket_name'
827
+ end
828
+ ```
805
829
 
830
+ For Google Storage XML API:
806
831
  ```ruby
807
832
  CarrierWave.configure do |config|
808
- config.fog_credentials = {
809
- provider: 'Google',
810
- google_storage_access_key_id: 'xxxxxx',
811
- google_storage_secret_access_key: 'yyyyyy'
812
- }
813
- config.fog_directory = 'name_of_directory'
833
+ config.fog_provider = 'fog/google'
834
+ config.fog_credentials = {
835
+ provider: 'Google',
836
+ google_storage_access_key_id: 'xxxxxx',
837
+ google_storage_secret_access_key: 'yyyyyy'
838
+ }
839
+ config.fog_directory = 'google_cloud_storage_bucket_name'
814
840
  end
815
841
  ```
816
842
 
@@ -954,10 +980,10 @@ errors:
954
980
  carrierwave_processing_error: failed to be processed
955
981
  carrierwave_integrity_error: is not of an allowed file type
956
982
  carrierwave_download_error: could not be downloaded
957
- extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
958
- extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
959
- content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
960
- content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
983
+ extension_allowlist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
984
+ extension_denylist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
985
+ content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
986
+ content_type_denylist_error: "You are not allowed to upload %{content_type} files"
961
987
  rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
962
988
  mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
963
989
  min_size_error: "File size should be greater than %{min_size}"
@@ -1005,12 +1031,12 @@ end
1005
1031
  Will add these callbacks:
1006
1032
 
1007
1033
  ```ruby
1008
- after_save :store_avatar!
1009
1034
  before_save :write_avatar_identifier
1035
+ after_save :store_previous_changes_for_avatar
1010
1036
  after_commit :remove_avatar!, on: :destroy
1011
1037
  after_commit :mark_remove_avatar_false, on: :update
1012
- after_save :store_previous_changes_for_avatar
1013
1038
  after_commit :remove_previously_stored_avatar, on: :update
1039
+ after_commit :store_avatar!, on: [:create, :update]
1014
1040
  ```
1015
1041
 
1016
1042
  If you want to skip any of these callbacks (eg. you want to keep the existing
@@ -1,4 +1,5 @@
1
1
  require 'open-uri'
2
+ require 'ssrf_filter'
2
3
  require 'addressable'
3
4
  require 'carrierwave/downloader/remote_file'
4
5
 
@@ -22,16 +23,26 @@ module CarrierWave
22
23
  def download(url, remote_headers = {})
23
24
  headers = remote_headers.
24
25
  reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
26
+ uri = process_uri(url.to_s)
25
27
  begin
26
- file = OpenURI.open_uri(process_uri(url.to_s), headers)
28
+ if skip_ssrf_protection?(uri)
29
+ response = OpenURI.open_uri(process_uri(url.to_s), headers)
30
+ else
31
+ request = nil
32
+ response = SsrfFilter.get(uri, headers: headers) do |req|
33
+ request = req
34
+ end
35
+ response.uri = request.uri
36
+ response.value
37
+ end
27
38
  rescue StandardError => e
28
39
  raise CarrierWave::DownloadError, "could not download file: #{e.message}"
29
40
  end
30
- CarrierWave::Downloader::RemoteFile.new(file)
41
+ CarrierWave::Downloader::RemoteFile.new(response)
31
42
  end
32
43
 
33
44
  ##
34
- # Processes the given URL by parsing and escaping it. Public to allow overriding.
45
+ # Processes the given URL by parsing it, and escaping if necessary. Public to allow overriding.
35
46
  #
36
47
  # === Parameters
37
48
  #
@@ -40,11 +51,37 @@ module CarrierWave
40
51
  def process_uri(uri)
41
52
  uri_parts = uri.split('?')
42
53
  encoded_uri = Addressable::URI.parse(uri_parts.shift).normalize.to_s
43
- encoded_uri << '?' << URI.encode(uri_parts.join('?')) if uri_parts.any?
44
- URI.parse(encoded_uri)
54
+ query = uri_parts.any? ? "?#{uri_parts.join('?')}" : ''
55
+ begin
56
+ URI.parse("#{encoded_uri}#{query}")
57
+ rescue URI::InvalidURIError
58
+ URI.parse("#{encoded_uri}#{URI::DEFAULT_PARSER.escape(query)}")
59
+ end
45
60
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
46
61
  raise CarrierWave::DownloadError, "couldn't parse URL: #{uri}"
47
62
  end
63
+
64
+ ##
65
+ # If this returns true, SSRF protection will be bypassed.
66
+ # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
67
+ #
68
+ # === Parameters
69
+ #
70
+ # [uri (URI)] The URI where the remote file is stored
71
+ #
72
+ # === Examples
73
+ #
74
+ # class CarrierWave::Downloader::CustomDownloader < CarrierWave::Downloader::Base
75
+ # def skip_ssrf_protection?(uri)
76
+ # uri.hostname == 'localhost' && uri.port == 80
77
+ # end
78
+ # end
79
+ #
80
+ # my_uploader.downloader = CarrierWave::Downloader::CustomDownloader
81
+ #
82
+ def skip_ssrf_protection?(uri)
83
+ false
84
+ end
48
85
  end
49
86
  end
50
87
  end
@@ -1,15 +1,36 @@
1
1
  module CarrierWave
2
2
  module Downloader
3
3
  class RemoteFile
4
- attr_reader :file
4
+ attr_reader :file, :uri
5
5
 
6
6
  def initialize(file)
7
- @file = file.is_a?(String) ? StringIO.new(file) : file
7
+ case file
8
+ when String
9
+ @file = StringIO.new(file)
10
+ when Net::HTTPResponse
11
+ @file = StringIO.new(file.body)
12
+ @content_type = file.content_type
13
+ @headers = file
14
+ @uri = file.uri
15
+ else
16
+ @file = file
17
+ @content_type = file.content_type
18
+ @headers = file.meta
19
+ @uri = file.base_uri
20
+ end
21
+ end
22
+
23
+ def content_type
24
+ @content_type || 'application/octet-stream'
25
+ end
26
+
27
+ def headers
28
+ @headers || {}
8
29
  end
9
30
 
10
31
  def original_filename
11
32
  filename = filename_from_header || filename_from_uri
12
- mime_type = MiniMime.lookup_by_content_type(file.content_type)
33
+ mime_type = MiniMime.lookup_by_content_type(content_type)
13
34
  unless File.extname(filename).present? || mime_type.blank?
14
35
  filename = "#{filename}.#{mime_type.extension}"
15
36
  end
@@ -23,14 +44,16 @@ module CarrierWave
23
44
  private
24
45
 
25
46
  def filename_from_header
26
- if file.meta.include? 'content-disposition'
27
- match = file.meta['content-disposition'].match(/filename=(?:"([^"]+)"|([^";]+))/)
28
- match[1].presence || match[2].presence
29
- end
47
+ return nil unless headers['content-disposition']
48
+
49
+ match = headers['content-disposition'].match(/filename=(?:"([^"]+)"|([^";]+))/)
50
+ return nil unless match
51
+
52
+ match[1].presence || match[2].presence
30
53
  end
31
54
 
32
55
  def filename_from_uri
33
- CGI.unescape(File.basename(file.base_uri.path))
56
+ CGI.unescape(File.basename(uri.path))
34
57
  end
35
58
 
36
59
  def method_missing(*args, &block)
@@ -4,11 +4,12 @@ en:
4
4
  carrierwave_processing_error: failed to be processed
5
5
  carrierwave_integrity_error: is not of an allowed file type
6
6
  carrierwave_download_error: could not be downloaded
7
- extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
- extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
9
- content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
- content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
7
+ extension_allowlist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
+ extension_denylist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
9
+ content_type_allowlist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
+ content_type_denylist_error: "You are not allowed to upload %{content_type} files"
11
11
  rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
12
12
  mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
13
+ vips_processing_error: "Failed to manipulate with vips, maybe it is not an image? Original Error: %{e}"
13
14
  min_size_error: "File size should be greater than %{min_size}"
14
15
  max_size_error: "File size should be less than %{max_size}"
@@ -72,9 +72,10 @@ module CarrierWave
72
72
  end
73
73
 
74
74
  def cache_names=(cache_names)
75
+ cache_names = cache_names.reject(&:blank?)
75
76
  return if cache_names.blank?
76
77
  clear_unstaged
77
- cache_names.map do |cache_name|
78
+ cache_names.each do |cache_name|
78
79
  begin
79
80
  uploader = blank_uploader
80
81
  uploader.retrieve_from_cache!(cache_name)
@@ -91,7 +92,7 @@ module CarrierWave
91
92
  @remote_urls = urls
92
93
 
93
94
  clear_unstaged
94
- urls.zip(remote_request_headers || []).map do |url, header|
95
+ urls.zip(remote_request_headers || []).each do |url, header|
95
96
  handle_error do
96
97
  uploader = blank_uploader
97
98
  uploader.download!(url, header || {})
@@ -113,7 +114,7 @@ module CarrierWave
113
114
  end
114
115
 
115
116
  def remove?
116
- remove.present? && remove !~ /\A0|false$\z/
117
+ remove.present? && (remove == true || remove !~ /\A0|false$\z/)
117
118
  end
118
119
 
119
120
  def remove!
@@ -1,2 +1,3 @@
1
1
  require "carrierwave/processing/rmagick"
2
2
  require "carrierwave/processing/mini_magick"
3
+ require "carrierwave/processing/vips"
@@ -297,7 +297,7 @@ module CarrierWave
297
297
 
298
298
  # backwards compatibility (we want to eventually move away from MiniMagick::Image)
299
299
  if block
300
- image = MiniMagick::Image.new(result.path, result)
300
+ image = ::MiniMagick::Image.new(result.path, result)
301
301
  image = block.call(image)
302
302
  result = image.instance_variable_get(:@tempfile)
303
303
  end
@@ -228,7 +228,7 @@ module CarrierWave
228
228
  height = dimension_from height
229
229
  manipulate! do |img|
230
230
  img.resize_to_fit!(width, height)
231
- new_img = ::Magick::Image.new(width, height) { self.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
231
+ new_img = ::Magick::Image.new(width, height) { |img| img.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
232
232
  if background == :transparent
233
233
  filled = new_img.matte_floodfill(1, 1)
234
234
  else
@@ -378,9 +378,15 @@ module CarrierWave
378
378
 
379
379
  def create_info_block(options)
380
380
  return nil unless options
381
- assignments = options.map { |k, v| "self.#{k} = #{v}" }
382
- code = "lambda { |img| " + assignments.join(";") + "}"
383
- eval code
381
+ proc do |img|
382
+ options.each do |k, v|
383
+ if v.is_a?(String) && (matches = v.match(/^["'](.+)["']/))
384
+ ActiveSupport::Deprecation.warn "Passing quoted strings like #{v} to #manipulate! is deprecated, pass them without quoting."
385
+ v = matches[1]
386
+ end
387
+ img.public_send(:"#{k}=", v)
388
+ end
389
+ end
384
390
  end
385
391
 
386
392
  def destroy_image(image)