carrierwave 1.3.2 → 2.2.2

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.

Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -43
  3. data/lib/carrierwave.rb +4 -0
  4. data/lib/carrierwave/downloader/base.rb +87 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +65 -0
  6. data/lib/carrierwave/locale/en.yml +5 -4
  7. data/lib/carrierwave/mount.rb +25 -19
  8. data/lib/carrierwave/mounter.rb +71 -48
  9. data/lib/carrierwave/orm/activerecord.rb +14 -8
  10. data/lib/carrierwave/processing.rb +1 -0
  11. data/lib/carrierwave/processing/mini_magick.rb +100 -117
  12. data/lib/carrierwave/processing/rmagick.rb +2 -2
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/sanitized_file.rb +38 -16
  15. data/lib/carrierwave/storage.rb +1 -0
  16. data/lib/carrierwave/storage/file.rb +2 -2
  17. data/lib/carrierwave/storage/fog.rb +44 -13
  18. data/lib/carrierwave/uploader.rb +0 -9
  19. data/lib/carrierwave/uploader/cache.rb +24 -16
  20. data/lib/carrierwave/uploader/configuration.rb +28 -15
  21. data/lib/carrierwave/uploader/content_type_blacklist.rb +17 -8
  22. data/lib/carrierwave/uploader/content_type_whitelist.rb +20 -8
  23. data/lib/carrierwave/uploader/download.rb +2 -123
  24. data/lib/carrierwave/uploader/extension_blacklist.rb +18 -10
  25. data/lib/carrierwave/uploader/extension_whitelist.rb +19 -10
  26. data/lib/carrierwave/uploader/mountable.rb +6 -0
  27. data/lib/carrierwave/uploader/proxy.rb +2 -2
  28. data/lib/carrierwave/uploader/serialization.rb +1 -1
  29. data/lib/carrierwave/uploader/store.rb +5 -3
  30. data/lib/carrierwave/uploader/url.rb +6 -3
  31. data/lib/carrierwave/uploader/versions.rb +43 -13
  32. data/lib/carrierwave/validations/active_model.rb +3 -3
  33. data/lib/carrierwave/version.rb +1 -1
  34. data/lib/generators/templates/uploader.rb +2 -2
  35. metadata +99 -20
@@ -43,15 +43,6 @@ module CarrierWave
43
43
  class Base
44
44
  attr_reader :file
45
45
 
46
- ##
47
- # Workaround for class_attribute malfunction when used with Module#prepend
48
- #
49
- if RUBY_VERSION < '2.1.0'
50
- def self.singleton_class?
51
- !ancestors.include? self
52
- end
53
- end
54
-
55
46
  include CarrierWave::Uploader::Configuration
56
47
  include CarrierWave::Uploader::Callbacks
57
48
  include CarrierWave::Uploader::Proxy
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module CarrierWave
2
4
 
3
5
  class FormNotMultipart < UploadError
@@ -23,9 +25,9 @@ module CarrierWave
23
25
  #
24
26
  def self.generate_cache_id
25
27
  [Time.now.utc.to_i,
26
- Process.pid,
27
- '%04d' % (CarrierWave::CacheCounter.increment % 1000),
28
- '%04d' % rand(9999)
28
+ SecureRandom.random_number(1_000_000_000_000_000),
29
+ '%04d' % (CarrierWave::CacheCounter.increment % 10_000),
30
+ '%04d' % SecureRandom.random_number(10_000)
29
31
  ].map(&:to_s).join('-')
30
32
  end
31
33
 
@@ -36,6 +38,16 @@ module CarrierWave
36
38
  include CarrierWave::Uploader::Callbacks
37
39
  include CarrierWave::Uploader::Configuration
38
40
 
41
+ included do
42
+ prepend Module.new {
43
+ def initialize(*)
44
+ super
45
+ @staged = false
46
+ end
47
+ }
48
+ attr_accessor :staged
49
+ end
50
+
39
51
  module ClassMethods
40
52
 
41
53
  ##
@@ -52,7 +64,7 @@ module CarrierWave
52
64
  # It's recommended that you keep cache files in one place only.
53
65
  #
54
66
  def clean_cached_files!(seconds=60*60*24)
55
- cache_storage.new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
67
+ (cache_storage || storage).new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
56
68
  end
57
69
  end
58
70
 
@@ -64,7 +76,7 @@ module CarrierWave
64
76
  # [Bool] whether the current file is cached
65
77
  #
66
78
  def cached?
67
- @cache_id
79
+ !!@cache_id
68
80
  end
69
81
 
70
82
  ##
@@ -78,14 +90,8 @@ module CarrierWave
78
90
  end
79
91
 
80
92
  def sanitized_file
81
- _content = file.read
82
- if _content.is_a?(File) # could be if storage is Fog
83
- sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path))
84
- else
85
- sanitized = SanitizedFile.new :tempfile => StringIO.new(_content),
86
- :filename => File.basename(path), :content_type => file.content_type
87
- end
88
- sanitized
93
+ ActiveSupport::Deprecation.warn('#sanitized_file is deprecated, use #file instead.')
94
+ file
89
95
  end
90
96
 
91
97
  ##
@@ -96,7 +102,7 @@ module CarrierWave
96
102
  # [String] a cache name, in the format TIMEINT-PID-COUNTER-RND/filename.txt
97
103
  #
98
104
  def cache_name
99
- File.join(cache_id, full_original_filename) if cache_id and original_filename
105
+ File.join(cache_id, full_original_filename) if cache_id && original_filename
100
106
  end
101
107
 
102
108
  ##
@@ -115,7 +121,7 @@ module CarrierWave
115
121
  #
116
122
  # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
117
123
  #
118
- def cache!(new_file = sanitized_file)
124
+ def cache!(new_file = file)
119
125
  new_file = CarrierWave::SanitizedFile.new(new_file)
120
126
  return if new_file.empty?
121
127
 
@@ -123,6 +129,7 @@ module CarrierWave
123
129
 
124
130
  self.cache_id = CarrierWave.generate_cache_id unless cache_id
125
131
 
132
+ @staged = true
126
133
  @filename = new_file.filename
127
134
  self.original_filename = new_file.filename
128
135
 
@@ -156,6 +163,7 @@ module CarrierWave
156
163
  def retrieve_from_cache!(cache_name)
157
164
  with_callbacks(:retrieve_from_cache, cache_name) do
158
165
  self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
166
+ @staged = true
159
167
  @filename = original_filename
160
168
  @file = cache_storage.retrieve_from_cache!(full_filename(original_filename))
161
169
  end
@@ -200,7 +208,7 @@ module CarrierWave
200
208
  end
201
209
 
202
210
  def cache_storage
203
- @cache_storage ||= self.class.cache_storage.new(self)
211
+ @cache_storage ||= (self.class.cache_storage || self.class.storage).new(self)
204
212
  end
205
213
  end # Cache
206
214
  end # Uploader
@@ -1,3 +1,5 @@
1
+ require 'carrierwave/downloader/base'
2
+
1
3
  module CarrierWave
2
4
 
3
5
  module Uploader
@@ -21,9 +23,10 @@ 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
24
27
 
25
28
  # fog
26
- add_config :fog_provider
29
+ add_deprecated_config :fog_provider
27
30
  add_config :fog_attributes
28
31
  add_config :fog_credentials
29
32
  add_config :fog_directory
@@ -107,8 +110,8 @@ module CarrierWave
107
110
  # cache_storage CarrierWave::Storage::File
108
111
  # cache_storage MyCustomStorageEngine
109
112
  #
110
- def cache_storage(storage = nil)
111
- if storage
113
+ def cache_storage(storage = false)
114
+ unless storage == false
112
115
  self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
113
116
  end
114
117
  _cache_storage
@@ -119,16 +122,8 @@ module CarrierWave
119
122
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
120
123
  @#{name} = nil
121
124
 
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
125
  def self.#{name}(value=nil)
130
126
  @#{name} = value if value
131
- eager_load_fog(value) if value && '#{name}' == 'fog_credentials'
132
127
  return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
133
128
  name = superclass.#{name}
134
129
  return nil if name.nil? && !instance_variable_defined?(:@#{name})
@@ -136,12 +131,10 @@ module CarrierWave
136
131
  end
137
132
 
138
133
  def self.#{name}=(value)
139
- eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
140
134
  @#{name} = value
141
135
  end
142
136
 
143
137
  def #{name}=(value)
144
- self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present?
145
138
  @#{name} = value
146
139
  end
147
140
 
@@ -157,6 +150,26 @@ module CarrierWave
157
150
  RUBY
158
151
  end
159
152
 
153
+ def add_deprecated_config(name)
154
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
155
+ def self.#{name}(value=nil)
156
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
157
+ end
158
+
159
+ def self.#{name}=(value)
160
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
161
+ end
162
+
163
+ def #{name}=(value)
164
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
165
+ end
166
+
167
+ def #{name}
168
+ ActiveSupport::Deprecation.warn "##{name} is deprecated and has no effect"
169
+ end
170
+ RUBY
171
+ end
172
+
160
173
  def configure
161
174
  yield self
162
175
  end
@@ -173,8 +186,7 @@ module CarrierWave
173
186
  :fog => "CarrierWave::Storage::Fog"
174
187
  }
175
188
  config.storage = :file
176
- config.cache_storage = :file
177
- config.fog_provider = 'fog'
189
+ config.cache_storage = nil
178
190
  config.fog_attributes = {}
179
191
  config.fog_credentials = {}
180
192
  config.fog_public = true
@@ -187,6 +199,7 @@ module CarrierWave
187
199
  config.move_to_cache = false
188
200
  config.move_to_store = false
189
201
  config.remove_previously_stored_files_after_update = true
202
+ config.downloader = CarrierWave::Downloader::Base
190
203
  config.ignore_integrity_errors = true
191
204
  config.ignore_processing_errors = true
192
205
  config.ignore_download_errors = true
@@ -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
@@ -8,39 +8,51 @@ module CarrierWave
8
8
  end
9
9
 
10
10
  ##
11
- # Override this method in your uploader to provide a whitelist of files content types
11
+ # Override this method in your uploader to provide an allowlist of files content types
12
12
  # which are 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 whitelist of content types which are allowed to be uploaded
17
+ # [NilClass, String, Regexp, Array[String, Regexp]] an allowlist of content types which are allowed to be uploaded
18
18
  #
19
19
  # === Examples
20
20
  #
21
- # def content_type_whitelist
21
+ # def content_type_allowlist
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_whitelist
27
+ # def content_type_allowlist
28
28
  # [/(text|application)\/json/]
29
29
  # end
30
30
  #
31
- def content_type_whitelist; end
31
+ def content_type_allowlist
32
+ if respond_to?(:content_type_whitelist)
33
+ ActiveSupport::Deprecation.warn "#content_type_whitelist is deprecated, use #content_type_allowlist instead." unless instance_variable_defined?(:@content_type_whitelist_warned)
34
+ @content_type_whitelist_warned = true
35
+ content_type_whitelist
36
+ end
37
+ end
32
38
 
33
39
  private
34
40
 
35
41
  def check_content_type_whitelist!(new_file)
42
+ return unless content_type_allowlist
43
+
36
44
  content_type = new_file.content_type
37
- if content_type_whitelist && !whitelisted_content_type?(content_type)
38
- raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_whitelist_error", content_type: content_type, allowed_types: Array(content_type_whitelist).join(", "))
45
+ if !whitelisted_content_type?(content_type)
46
+ raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.content_type_whitelist_error", content_type: content_type,
47
+ allowed_types: Array(content_type_allowlist).join(", "), default: :"errors.messages.content_type_allowlist_error")
39
48
  end
40
49
  end
41
50
 
42
51
  def whitelisted_content_type?(content_type)
43
- Array(content_type_whitelist).any? { |item| content_type =~ /#{item}/ }
52
+ Array(content_type_allowlist).any? do |item|
53
+ item = Regexp.quote(item) if item.class != Regexp
54
+ content_type =~ /#{item}/
55
+ end
44
56
  end
45
57
 
46
58
  end # ContentTypeWhitelist
@@ -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