carrierwave 1.3.4 → 3.1.2

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +278 -86
  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 +169 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  10. data/lib/carrierwave/processing/mini_magick.rb +137 -123
  11. data/lib/carrierwave/processing/rmagick.rb +47 -20
  12. data/lib/carrierwave/processing/vips.rb +315 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +83 -70
  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 +114 -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 +42 -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 +45 -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 +73 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +161 -111
  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 +22 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -4
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +94 -47
  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,3 +1,5 @@
1
+ require 'carrierwave/downloader/base'
2
+
1
3
  module CarrierWave
2
4
 
3
5
  module Uploader
@@ -21,9 +23,11 @@ module CarrierWave
21
23
  add_config :move_to_cache
22
24
  add_config :move_to_store
23
25
  add_config :remove_previously_stored_files_after_update
26
+ add_config :downloader
27
+ add_config :force_extension
24
28
 
25
29
  # fog
26
- add_config :fog_provider
30
+ add_deprecated_config :fog_provider
27
31
  add_config :fog_attributes
28
32
  add_config :fog_credentials
29
33
  add_config :fog_directory
@@ -31,6 +35,7 @@ module CarrierWave
31
35
  add_config :fog_authenticated_url_expiration
32
36
  add_config :fog_use_ssl_for_aws
33
37
  add_config :fog_aws_accelerate
38
+ add_config :fog_aws_fips
34
39
 
35
40
  # Mounting
36
41
  add_config :ignore_integrity_errors
@@ -41,6 +46,9 @@ module CarrierWave
41
46
  add_config :validate_download
42
47
  add_config :mount_on
43
48
  add_config :cache_only
49
+ add_config :download_retry_count
50
+ add_config :download_retry_wait_time
51
+ add_config :skip_ssrf_protection
44
52
 
45
53
  # set default values
46
54
  reset_config
@@ -74,7 +82,7 @@ module CarrierWave
74
82
  def storage(storage = nil)
75
83
  case storage
76
84
  when Symbol
77
- if storage_engine = storage_engines[storage]
85
+ if (storage_engine = storage_engines[storage])
78
86
  self._storage = eval storage_engine
79
87
  else
80
88
  raise CarrierWave::UnknownStorageError, "Unknown storage: #{storage}"
@@ -107,8 +115,8 @@ module CarrierWave
107
115
  # cache_storage CarrierWave::Storage::File
108
116
  # cache_storage MyCustomStorageEngine
109
117
  #
110
- def cache_storage(storage = nil)
111
- if storage
118
+ def cache_storage(storage = false)
119
+ unless storage == false
112
120
  self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
113
121
  end
114
122
  _cache_storage
@@ -119,16 +127,8 @@ module CarrierWave
119
127
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
120
128
  @#{name} = nil
121
129
 
122
- def self.eager_load_fog(fog_credentials)
123
- # see #1198. This will hopefully no longer be necessary after fog 2.0
124
- require self.fog_provider
125
- require 'carrierwave/storage/fog'
126
- Fog::Storage.new(fog_credentials) if fog_credentials.present?
127
- end unless defined? eager_load_fog
128
-
129
130
  def self.#{name}(value=nil)
130
- @#{name} = value if value
131
- eager_load_fog(value) if value && '#{name}' == 'fog_credentials'
131
+ @#{name} = value unless value.nil?
132
132
  return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
133
133
  name = superclass.#{name}
134
134
  return nil if name.nil? && !instance_variable_defined?(:@#{name})
@@ -136,12 +136,10 @@ module CarrierWave
136
136
  end
137
137
 
138
138
  def self.#{name}=(value)
139
- eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
140
139
  @#{name} = value
141
140
  end
142
141
 
143
142
  def #{name}=(value)
144
- self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
145
143
  @#{name} = value
146
144
  end
147
145
 
@@ -157,6 +155,26 @@ module CarrierWave
157
155
  RUBY
158
156
  end
159
157
 
158
+ def add_deprecated_config(name)
159
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
160
+ def self.#{name}(value=nil)
161
+ CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
162
+ end
163
+
164
+ def self.#{name}=(value)
165
+ CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
166
+ end
167
+
168
+ def #{name}=(value)
169
+ CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
170
+ end
171
+
172
+ def #{name}
173
+ CarrierWave.deprecator.warn "##{name} is deprecated and has no effect"
174
+ end
175
+ RUBY
176
+ end
177
+
160
178
  def configure
161
179
  yield self
162
180
  end
@@ -166,27 +184,29 @@ module CarrierWave
166
184
  #
167
185
  def reset_config
168
186
  configure do |config|
169
- config.permissions = 0644
170
- config.directory_permissions = 0755
187
+ config.permissions = 0o644
188
+ config.directory_permissions = 0o755
171
189
  config.storage_engines = {
172
190
  :file => "CarrierWave::Storage::File",
173
191
  :fog => "CarrierWave::Storage::Fog"
174
192
  }
175
193
  config.storage = :file
176
- config.cache_storage = :file
177
- config.fog_provider = 'fog'
194
+ config.cache_storage = nil
178
195
  config.fog_attributes = {}
179
196
  config.fog_credentials = {}
180
197
  config.fog_public = true
181
198
  config.fog_authenticated_url_expiration = 600
182
199
  config.fog_use_ssl_for_aws = true
183
200
  config.fog_aws_accelerate = false
201
+ config.fog_aws_fips = false
184
202
  config.store_dir = 'uploads'
185
203
  config.cache_dir = 'uploads/tmp'
186
204
  config.delete_tmp_file_after_storage = true
187
205
  config.move_to_cache = false
188
206
  config.move_to_store = false
189
207
  config.remove_previously_stored_files_after_update = true
208
+ config.downloader = CarrierWave::Downloader::Base
209
+ config.force_extension = false
190
210
  config.ignore_integrity_errors = true
191
211
  config.ignore_processing_errors = true
192
212
  config.ignore_download_errors = true
@@ -197,6 +217,9 @@ module CarrierWave
197
217
  config.base_path = CarrierWave.base_path
198
218
  config.enable_processing = true
199
219
  config.ensure_multipart_form = true
220
+ config.download_retry_count = 0
221
+ config.download_retry_wait_time = 5
222
+ config.skip_ssrf_protection = false
200
223
  end
201
224
  end
202
225
  end
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeAllowlist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_allowlist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide an allowlist of files content types
12
+ # which are allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of content types which are allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_allowlist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_allowlist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_allowlist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_allowlist!(new_file)
37
+ allowlist = content_type_allowlist
38
+ if !allowlist && respond_to?(:content_type_whitelist) && content_type_whitelist
39
+ CarrierWave.deprecator.warn "#content_type_whitelist is deprecated, use #content_type_allowlist instead." unless instance_variable_defined?(:@content_type_whitelist_warned)
40
+ @content_type_whitelist_warned = true
41
+ allowlist = content_type_whitelist
42
+ end
43
+
44
+ return unless allowlist
45
+
46
+ content_type = new_file.content_type
47
+ if !allowlisted_content_type?(allowlist, content_type)
48
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_allowlist_error", content_type: content_type,
49
+ allowed_types: Array(allowlist).join(", "), default: :"errors.messages.content_type_whitelist_error")
50
+ end
51
+ end
52
+
53
+ def allowlisted_content_type?(allowlist, content_type)
54
+ Array(allowlist).any? do |item|
55
+ item = Regexp.quote(item) if item.class != Regexp
56
+ content_type =~ /\A#{item}/
57
+ end
58
+ end
59
+
60
+ end # ContentTypeAllowlist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -0,0 +1,62 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ContentTypeDenylist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_content_type_denylist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a denylist of files content types
12
+ # which are not allowed to be uploaded.
13
+ # Not only strings but Regexp are allowed as well.
14
+ #
15
+ # === Returns
16
+ #
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] a denylist of content types which are not allowed to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def content_type_denylist
22
+ # %w(text/json application/json)
23
+ # end
24
+ #
25
+ # Basically the same, but using a Regexp:
26
+ #
27
+ # def content_type_denylist
28
+ # [/(text|application)\/json/]
29
+ # end
30
+ #
31
+ def content_type_denylist
32
+ end
33
+
34
+ private
35
+
36
+ def check_content_type_denylist!(new_file)
37
+ denylist = content_type_denylist
38
+ if !denylist && respond_to?(:content_type_blacklist) && content_type_blacklist
39
+ CarrierWave.deprecator.warn "#content_type_blacklist is deprecated, use #content_type_denylist instead." unless instance_variable_defined?(:@content_type_blacklist_warned)
40
+ @content_type_blacklist_warned = true
41
+ denylist = content_type_blacklist
42
+ end
43
+
44
+ return unless denylist
45
+
46
+ CarrierWave.deprecator.warn "Use of #content_type_denylist is deprecated for the security reason, use #content_type_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@content_type_denylist_warned)
47
+ @content_type_denylist_warned = true
48
+
49
+ content_type = new_file.content_type
50
+ if denylisted_content_type?(denylist, content_type)
51
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_denylist_error",
52
+ content_type: content_type, default: :"errors.messages.content_type_blacklist_error")
53
+ end
54
+ end
55
+
56
+ def denylisted_content_type?(denylist, content_type)
57
+ Array(denylist).any? { |item| content_type =~ /#{item}/ }
58
+ end
59
+
60
+ end # ContentTypeDenylist
61
+ end # Uploader
62
+ end # CarrierWave
@@ -0,0 +1,66 @@
1
+ require 'active_support'
2
+
3
+ module CarrierWave
4
+ module Uploader
5
+ module Dimension
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before :cache, :check_dimensions!
10
+ end
11
+
12
+ ##
13
+ # Override this method in your uploader to provide a Range of width which
14
+ # are allowed to be uploaded.
15
+ # === Returns
16
+ #
17
+ # [NilClass, Range] a width range which are permitted to be uploaded
18
+ #
19
+ # === Examples
20
+ #
21
+ # def width_range
22
+ # 1000..2000
23
+ # end
24
+ #
25
+ def width_range; end
26
+
27
+ ##
28
+ # Override this method in your uploader to provide a Range of height which
29
+ # are allowed to be uploaded.
30
+ # === Returns
31
+ #
32
+ # [NilClass, Range] a height range which are permitted to be uploaded
33
+ #
34
+ # === Examples
35
+ #
36
+ # def height_range
37
+ # 1000..
38
+ # end
39
+ #
40
+ def height_range; end
41
+
42
+ private
43
+
44
+ def check_dimensions!(new_file)
45
+ # NOTE: Skip the check for resized images
46
+ return if version_name.present?
47
+ return unless width_range || height_range
48
+
49
+ unless respond_to?(:width) || respond_to?(:height)
50
+ raise 'You need to include one of CarrierWave::MiniMagick, CarrierWave::RMagick, or CarrierWave::Vips to perform image dimension validation'
51
+ end
52
+
53
+ if width_range&.begin && width < width_range.begin
54
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_width_error", :min_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.begin))
55
+ elsif width_range&.end && width > width_range.end
56
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_width_error", :max_width => ActiveSupport::NumberHelper.number_to_delimited(width_range.end))
57
+ elsif height_range&.begin && height < height_range.begin
58
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_height_error", :min_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.begin))
59
+ elsif height_range&.end && height > height_range.end
60
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_height_error", :max_height => ActiveSupport::NumberHelper.number_to_delimited(height_range.end))
61
+ end
62
+ end
63
+
64
+ end # Dimension
65
+ end # Uploader
66
+ end # CarrierWave
@@ -1,6 +1,3 @@
1
- require 'open-uri'
2
- require 'ssrf_filter'
3
-
4
1
  module CarrierWave
5
2
  module Uploader
6
3
  module Download
@@ -10,87 +7,8 @@ module CarrierWave
10
7
  include CarrierWave::Uploader::Configuration
11
8
  include CarrierWave::Uploader::Cache
12
9
 
13
- class RemoteFile
14
- attr_reader :uri
15
-
16
- def initialize(uri, remote_headers = {}, skip_ssrf_protection: false)
17
- @uri = uri
18
- @remote_headers = remote_headers.reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
19
- @file, @content_type, @headers = nil
20
- @skip_ssrf_protection = skip_ssrf_protection
21
- end
22
-
23
- def original_filename
24
- filename = filename_from_header || filename_from_uri
25
- mime_type = MIME::Types[content_type].first
26
- unless File.extname(filename).present? || mime_type.blank?
27
- filename = "#{filename}.#{mime_type.extensions.first}"
28
- end
29
- filename
30
- end
31
-
32
- def respond_to?(*args)
33
- super or file.respond_to?(*args)
34
- end
35
-
36
- def http?
37
- @uri.scheme =~ /^https?$/
38
- end
39
-
40
- def content_type
41
- @content_type || 'application/octet-stream'
42
- end
43
-
44
- def headers
45
- @headers || {}
46
- end
47
-
48
- private
49
-
50
- def file
51
- if @file.blank?
52
- if @skip_ssrf_protection
53
- @file = (URI.respond_to?(:open) ? URI : Kernel).open(@uri.to_s, @remote_headers)
54
- @file = @file.is_a?(String) ? StringIO.new(@file) : @file
55
- @content_type = @file.content_type
56
- @headers = @file.meta
57
- @uri = @file.base_uri
58
- else
59
- request = nil
60
- response = SsrfFilter.get(@uri, headers: @remote_headers) do |req|
61
- request = req
62
- end
63
- response.value
64
- @file = StringIO.new(response.body)
65
- @content_type = response.content_type
66
- @headers = response
67
- @uri = request.uri
68
- end
69
- end
70
- @file
71
-
72
- rescue StandardError => e
73
- raise CarrierWave::DownloadError, "could not download file: #{e.message}"
74
- end
75
-
76
- def filename_from_header
77
- if headers['content-disposition']
78
- match = headers['content-disposition'].match(/filename="?([^"]+)/)
79
- return match[1] unless match.nil? || match[1].empty?
80
- end
81
- end
82
-
83
- def filename_from_uri
84
- URI::DEFAULT_PARSER.unescape(File.basename(@uri.path))
85
- end
86
-
87
- def method_missing(*args, &block)
88
- file.send(*args, &block)
89
- end
90
- end
91
-
92
10
  ##
93
- # Caches the file by downloading it from the given URL.
11
+ # Caches the file by downloading it from the given URL, using downloader.
94
12
  #
95
13
  # === Parameters
96
14
  #
@@ -98,48 +16,9 @@ module CarrierWave
98
16
  # [remote_headers (Hash)] Request headers
99
17
  #
100
18
  def download!(uri, remote_headers = {})
101
- processed_uri = process_uri(uri)
102
- file = RemoteFile.new(processed_uri, remote_headers, skip_ssrf_protection: skip_ssrf_protection?(processed_uri))
103
- raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http?
19
+ file = downloader.new(self).download(uri, remote_headers)
104
20
  cache!(file)
105
21
  end
106
-
107
- ##
108
- # Processes the given URL by parsing and escaping it. Public to allow overriding.
109
- #
110
- # === Parameters
111
- #
112
- # [url (String)] The URL where the remote file is stored
113
- #
114
- def process_uri(uri)
115
- URI.parse(uri)
116
- rescue URI::InvalidURIError
117
- uri_parts = uri.split('?')
118
- # regexp from Ruby's URI::Parser#regexp[:UNSAFE], with [] specifically removed
119
- encoded_uri = URI::DEFAULT_PARSER.escape(uri_parts.shift, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/)
120
- encoded_uri << '?' << URI::DEFAULT_PARSER.escape(uri_parts.join('?')) if uri_parts.any?
121
- URI.parse(encoded_uri) rescue raise CarrierWave::DownloadError, "couldn't parse URL"
122
- end
123
-
124
- ##
125
- # If this returns true, SSRF protection will be bypassed.
126
- # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
127
- #
128
- # === Parameters
129
- #
130
- # [uri (URI)] The URI where the remote file is stored
131
- #
132
- # === Examples
133
- #
134
- # class MyUploader < CarrierWave::Uploader::Base
135
- # def skip_ssrf_protection?(uri)
136
- # uri.hostname == 'localhost' && uri.port == 80
137
- # end
138
- # end
139
- #
140
- def skip_ssrf_protection?(uri)
141
- false
142
- end
143
22
  end # Download
144
23
  end # Uploader
145
24
  end # CarrierWave
@@ -0,0 +1,63 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ExtensionAllowlist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_extension_allowlist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide an allowlist of extensions which
12
+ # are allowed to be uploaded. Compares the file's extension case insensitive.
13
+ # Furthermore, not only strings but Regexp are allowed as well.
14
+ #
15
+ # When using a Regexp in the allowlist, `\A` and `\z` are automatically added to
16
+ # the Regexp expression, also case insensitive.
17
+ #
18
+ # === Returns
19
+ #
20
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of extensions which are allowed to be uploaded
21
+ #
22
+ # === Examples
23
+ #
24
+ # def extension_allowlist
25
+ # %w(jpg jpeg gif png)
26
+ # end
27
+ #
28
+ # Basically the same, but using a Regexp:
29
+ #
30
+ # def extension_allowlist
31
+ # [/jpe?g/, 'gif', 'png']
32
+ # end
33
+ #
34
+ def extension_allowlist
35
+ end
36
+
37
+ private
38
+
39
+ def check_extension_allowlist!(new_file)
40
+ allowlist = extension_allowlist
41
+ if !allowlist && respond_to?(:extension_whitelist) && extension_whitelist
42
+ CarrierWave.deprecator.warn "#extension_whitelist is deprecated, use #extension_allowlist instead." unless instance_variable_defined?(:@extension_whitelist_warned)
43
+ @extension_whitelist_warned = true
44
+ allowlist = extension_whitelist
45
+ end
46
+
47
+ return unless allowlist
48
+
49
+ extension = new_file.extension.to_s
50
+ if !allowlisted_extension?(allowlist, extension)
51
+ # Look for whitelist first, then fallback to allowlist
52
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_allowlist_error", extension: new_file.extension.inspect,
53
+ allowed_types: Array(allowlist).join(", "), default: :"errors.messages.extension_whitelist_error")
54
+ end
55
+ end
56
+
57
+ def allowlisted_extension?(allowlist, extension)
58
+ downcase_extension = extension.downcase
59
+ Array(allowlist).any? { |item| downcase_extension =~ /\A#{item}\z/i }
60
+ end
61
+ end # ExtensionAllowlist
62
+ end # Uploader
63
+ end # CarrierWave
@@ -0,0 +1,64 @@
1
+ module CarrierWave
2
+ module Uploader
3
+ module ExtensionDenylist
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before :cache, :check_extension_denylist!
8
+ end
9
+
10
+ ##
11
+ # Override this method in your uploader to provide a denylist of extensions which
12
+ # are prohibited to be uploaded. Compares the file's extension case insensitive.
13
+ # Furthermore, not only strings but Regexp are allowed as well.
14
+ #
15
+ # When using a Regexp in the denylist, `\A` and `\z` are automatically added to
16
+ # the Regexp expression, also case insensitive.
17
+ #
18
+ # === Returns
19
+
20
+ # [NilClass, String, Regexp, Array[String, Regexp]] a deny list of extensions which are prohibited to be uploaded
21
+ #
22
+ # === Examples
23
+ #
24
+ # def extension_denylist
25
+ # %w(swf tiff)
26
+ # end
27
+ #
28
+ # Basically the same, but using a Regexp:
29
+ #
30
+ # def extension_denylist
31
+ # [/swf/, 'tiff']
32
+ # end
33
+ #
34
+ def extension_denylist
35
+ end
36
+
37
+ private
38
+
39
+ def check_extension_denylist!(new_file)
40
+ denylist = extension_denylist
41
+ if !denylist && respond_to?(:extension_blacklist) && extension_blacklist
42
+ CarrierWave.deprecator.warn "#extension_blacklist is deprecated, use #extension_denylist instead." unless instance_variable_defined?(:@extension_blacklist_warned)
43
+ @extension_blacklist_warned = true
44
+ denylist = extension_blacklist
45
+ end
46
+
47
+ return unless denylist
48
+
49
+ CarrierWave.deprecator.warn "Use of #extension_denylist is deprecated for the security reason, use #extension_allowlist instead to explicitly state what are safe to accept" unless instance_variable_defined?(:@extension_denylist_warned)
50
+ @extension_denylist_warned = true
51
+
52
+ extension = new_file.extension.to_s
53
+ if denylisted_extension?(denylist, extension)
54
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_denylist_error", extension: new_file.extension.inspect,
55
+ prohibited_types: Array(extension_denylist).join(", "), default: :"errors.messages.extension_blacklist_error")
56
+ end
57
+ end
58
+
59
+ def denylisted_extension?(denylist, extension)
60
+ Array(denylist).any? { |item| extension =~ /\A#{item}\z/i }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -14,7 +14,7 @@ module CarrierWave
14
14
  # are allowed to be uploaded.
15
15
  # === Returns
16
16
  #
17
- # [NilClass, Range] a size range which are permitted to be uploaded
17
+ # [NilClass, Range] a size range (in bytes) which are permitted to be uploaded
18
18
  #
19
19
  # === Examples
20
20
  #
@@ -24,7 +24,7 @@ module CarrierWave
24
24
  #
25
25
  def size_range; end
26
26
 
27
- private
27
+ private
28
28
 
29
29
  def check_size!(new_file)
30
30
  size = new_file.size
@@ -33,6 +33,12 @@ module CarrierWave
33
33
  @mounted_as = mounted_as
34
34
  end
35
35
 
36
+ ##
37
+ # Returns array index of given uploader within currently mounted uploaders
38
+ #
39
+ def index
40
+ model.__send__(:_mounter, mounted_as).uploaders.index(self)
41
+ end
36
42
  end # Mountable
37
43
  end # Uploader
38
44
  end # CarrierWave