carrierwave 1.3.4 → 2.0.0.rc

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: 8424c8e8238faa3fd7fda7424149843ad11dc06e7704e55412a905c765ac0a0c
4
- data.tar.gz: ecef054ec6f134badaaffbc406794ad3642e000e8a8623d9e63655e726ea2c7c
3
+ metadata.gz: 8d930fb9703b4bdb5ec81e14a01a5f07a260f39b52caed16978f193ddfd73159
4
+ data.tar.gz: 83facb0af8dd50905b36510986a0215d3e6a03b765e83b6b063a6bc8ad7bbaf0
5
5
  SHA512:
6
- metadata.gz: 5d01517bae4bca3457d5576739c51fa2c9ba0ca54a72060b0c673268ae6dc8199f309891c4bc5212ec64019055a0ea40fe853a208ec74cbaf5cc325c4f8888ab
7
- data.tar.gz: edc2a20b818d2cdbabb71aee399de99a08ed6a95096e6e634c6213403368704b0871b88ff81d0ec6f1b0dd7661c4bec9dc78c524105bf6ecdb6e28e1f30028b7
6
+ metadata.gz: aa813176c7dca0e42e9e131d405eeec2324741c7dff79fc4ceed9c5d16dabb52d967f6e40a025fe431412d91e1cfcb9fd043655271de7d615938f89f9f075179
7
+ data.tar.gz: 16877c3b9ce32913719f0329b300bae18991de3d642702eed202639a2f9ecb0613e41c766f0ae9adf97d2d64247f86cb3cbdb47f75112542ba01dd82b3653539
data/README.md CHANGED
@@ -30,13 +30,13 @@ $ gem install carrierwave
30
30
  In Rails, add it to your Gemfile:
31
31
 
32
32
  ```ruby
33
- gem 'carrierwave', '~> 1.0'
33
+ gem 'carrierwave', '>= 2.0.0.rc', '< 3.0'
34
34
  ```
35
35
 
36
36
  Finally, restart the server to apply the changes.
37
37
 
38
- As of version 1.0, CarrierWave requires Rails 4.0 or higher and Ruby 2.0
39
- or higher. If you're on Rails 3, you should use v0.11.0.
38
+ As of version 2.0, CarrierWave requires Rails 5.0 or higher and Ruby 2.2
39
+ or higher. If you're on Rails 4, you should use 1.x.
40
40
 
41
41
  ## Getting Started
42
42
 
@@ -190,6 +190,17 @@ u.avatars[0].current_path # => 'path/to/file.png'
190
190
  u.avatars[0].identifier # => 'file.png'
191
191
  ```
192
192
 
193
+ If you want to preserve existing files on uploading new one, you can go like:
194
+
195
+ ```erb
196
+ <% user.avatars.each do |avatar| %>
197
+ <%= hidden_field :user, :avatars, multiple: true, value: avatar.identifier %>
198
+ <% end %>
199
+ <%= form.file_field :avatars, multiple: true %>
200
+ ```
201
+
202
+ Sorting avatars is supported as well by reordering `hidden_field`, an example using jQuery UI Sortable is available [here](https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Add%2C-remove-and-reorder-images-using-multiple-file-upload).
203
+
193
204
  ## Changing the storage directory
194
205
 
195
206
  In order to change where uploaded files are put, just override the `store_dir`
@@ -255,6 +266,22 @@ class NoJsonUploader < CarrierWave::Uploader::Base
255
266
  end
256
267
  ```
257
268
 
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.
271
+
272
+
273
+ A valid whitelist that will restrict your uploader to images only, and mitigate the CVE is:
274
+
275
+ ```ruby
276
+ class MyUploader < CarrierWave::Uploader::Base
277
+ def content_type_whitelist
278
+ [/image\//]
279
+ end
280
+ end
281
+ ```
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.
284
+
258
285
  ### Filenames and unicode chars
259
286
 
260
287
  Another security issue you should care for is the file names (see
@@ -280,7 +307,7 @@ You no longer need to do this manually.
280
307
 
281
308
  Often you'll want to add different versions of the same file. The classic example is image thumbnails. There is built in support for this*:
282
309
 
283
- *Note:* You must have Imagemagick and MiniMagick installed to do image resizing. MiniMagick is a Ruby interface for Imagemagick which is a C program. This is why MiniMagick fails on 'bundle install' without Imagemagick installed.
310
+ *Note:* You must have Imagemagick installed to do image resizing.
284
311
 
285
312
  Some documentation refers to RMagick instead of MiniMagick but MiniMagick is recommended.
286
313
 
@@ -362,7 +389,7 @@ private
362
389
  end
363
390
 
364
391
  def is_landscape? picture
365
- image = MiniMagick::Image.open(picture.path)
392
+ image = MiniMagick::Image.new(picture.path)
366
393
  image[:width] > image[:height]
367
394
  end
368
395
 
@@ -651,7 +678,6 @@ If you want to use fog you must add in your CarrierWave initializer the
651
678
  following lines
652
679
 
653
680
  ```ruby
654
- config.fog_provider = 'fog' # 'fog/aws' etc. Defaults to 'fog'
655
681
  config.fog_credentials = { ... } # Provider specific credentials
656
682
  ```
657
683
 
@@ -669,7 +695,6 @@ You can also pass in additional options, as documented fully in lib/carrierwave/
669
695
 
670
696
  ```ruby
671
697
  CarrierWave.configure do |config|
672
- config.fog_provider = 'fog/aws' # required
673
698
  config.fog_credentials = {
674
699
  provider: 'AWS', # required
675
700
  aws_access_key_id: 'xxx', # required unless using use_iam_profile
@@ -695,6 +720,14 @@ end
695
720
 
696
721
  That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Amazon S3.
697
722
 
723
+ **Note**: for Carrierwave to work properly it needs credentials with the following permissions:
724
+
725
+ * `s3:ListBucket`
726
+ * `s3:PutObject`
727
+ * `s3:GetObject`
728
+ * `s3:DeleteObject`
729
+ * `s3:PutObjectAcl`
730
+
698
731
  ## Using Rackspace Cloud Files
699
732
 
700
733
  [Fog](http://github.com/fog/fog) is used to support Rackspace Cloud Files. Ensure you have it in your Gemfile:
@@ -710,7 +743,6 @@ Using a US-based account:
710
743
 
711
744
  ```ruby
712
745
  CarrierWave.configure do |config|
713
- config.fog_provider = "fog/rackspace/storage" # optional, defaults to "fog"
714
746
  config.fog_credentials = {
715
747
  provider: 'Rackspace',
716
748
  rackspace_username: 'xxxxxx',
@@ -725,7 +757,6 @@ Using a UK-based account:
725
757
 
726
758
  ```ruby
727
759
  CarrierWave.configure do |config|
728
- config.fog_provider = "fog/rackspace/storage" # optional, defaults to "fog"
729
760
  config.fog_credentials = {
730
761
  provider: 'Rackspace',
731
762
  rackspace_username: 'xxxxxx',
@@ -774,7 +805,6 @@ Please read the [fog-google README](https://github.com/fog/fog-google/blob/maste
774
805
 
775
806
  ```ruby
776
807
  CarrierWave.configure do |config|
777
- config.fog_provider = 'fog/google' # required
778
808
  config.fog_credentials = {
779
809
  provider: 'Google',
780
810
  google_storage_access_key_id: 'xxxxxx',
@@ -871,8 +901,8 @@ manipulation methods.
871
901
 
872
902
  ## Using MiniMagick
873
903
 
874
- MiniMagick is similar to RMagick but performs all the operations using the 'mogrify'
875
- command which is part of the standard ImageMagick kit. This allows you to have the power
904
+ MiniMagick is similar to RMagick but performs all the operations using the 'convert'
905
+ CLI which is part of the standard ImageMagick kit. This allows you to have the power
876
906
  of ImageMagick without having to worry about installing all the RMagick libraries.
877
907
 
878
908
  See the MiniMagick site for more details:
@@ -0,0 +1,50 @@
1
+ require 'open-uri'
2
+ require 'addressable'
3
+ require 'carrierwave/downloader/remote_file'
4
+
5
+ module CarrierWave
6
+ module Downloader
7
+ class Base
8
+ attr_reader :uploader
9
+
10
+ def initialize(uploader)
11
+ @uploader = uploader
12
+ end
13
+
14
+ ##
15
+ # Downloads a file from given URL and returns a RemoteFile.
16
+ #
17
+ # === Parameters
18
+ #
19
+ # [url (String)] The URL where the remote file is stored
20
+ # [remote_headers (Hash)] Request headers
21
+ #
22
+ def download(url, remote_headers = {})
23
+ headers = remote_headers.
24
+ reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
25
+ begin
26
+ file = OpenURI.open_uri(process_uri(url.to_s), headers)
27
+ rescue StandardError => e
28
+ raise CarrierWave::DownloadError, "could not download file: #{e.message}"
29
+ end
30
+ CarrierWave::Downloader::RemoteFile.new(file)
31
+ end
32
+
33
+ ##
34
+ # Processes the given URL by parsing and escaping it. Public to allow overriding.
35
+ #
36
+ # === Parameters
37
+ #
38
+ # [url (String)] The URL where the remote file is stored
39
+ #
40
+ def process_uri(uri)
41
+ uri_parts = uri.split('?')
42
+ 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)
45
+ rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
46
+ raise CarrierWave::DownloadError, "couldn't parse URL: #{uri}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ module CarrierWave
2
+ module Downloader
3
+ class RemoteFile
4
+ attr_reader :file
5
+
6
+ def initialize(file)
7
+ @file = file.is_a?(String) ? StringIO.new(file) : file
8
+ end
9
+
10
+ def original_filename
11
+ filename = filename_from_header || filename_from_uri
12
+ mime_type = MiniMime.lookup_by_content_type(file.content_type)
13
+ unless File.extname(filename).present? || mime_type.blank?
14
+ filename = "#{filename}.#{mime_type.extension}"
15
+ end
16
+ filename
17
+ end
18
+
19
+ def respond_to?(*args)
20
+ super || file.respond_to?(*args)
21
+ end
22
+
23
+ private
24
+
25
+ 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
30
+ end
31
+
32
+ def filename_from_uri
33
+ CGI.unescape(File.basename(file.base_uri.path))
34
+ end
35
+
36
+ def method_missing(*args, &block)
37
+ file.send(*args, &block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -174,17 +174,26 @@ module CarrierWave
174
174
  return if frozen?
175
175
  mounter = _mounter(:#{column})
176
176
 
177
- if mounter.remove?
178
- write_uploader(mounter.serialization_column, nil)
179
- elsif mounter.identifiers.first
180
- write_uploader(mounter.serialization_column, mounter.identifiers.first)
181
- end
177
+ mounter.clear! if mounter.remove?
178
+ write_uploader(mounter.serialization_column, mounter.identifiers.first)
182
179
  end
183
180
 
184
181
  def #{column}_identifier
185
182
  _mounter(:#{column}).read_identifiers[0]
186
183
  end
187
184
 
185
+ def #{column}_integrity_error
186
+ #{column}_integrity_errors.last
187
+ end
188
+
189
+ def #{column}_processing_error
190
+ #{column}_processing_errors.last
191
+ end
192
+
193
+ def #{column}_download_error
194
+ #{column}_download_errors.last
195
+ end
196
+
188
197
  def store_previous_changes_for_#{column}
189
198
  attribute_changes = ::ActiveRecord.version.to_s.to_f >= 5.1 ? saved_changes : changes
190
199
  @_previous_changes_for_#{column} = attribute_changes[_mounter(:#{column}).serialization_column]
@@ -240,9 +249,9 @@ module CarrierWave
240
249
  # [store_images!] Stores all files that have been assigned with +images=+
241
250
  # [remove_images!] Removes the uploaded file from the filesystem.
242
251
  #
243
- # [images_integrity_error] Returns an error object if the last files to be assigned caused an integrity error
244
- # [images_processing_error] Returns an error object if the last files to be assigned caused a processing error
245
- # [images_download_error] Returns an error object if the last files to be remotely assigned caused a download error
252
+ # [image_integrity_errors] Returns error objects of files which failed to pass integrity check
253
+ # [image_processing_errors] Returns error objects of files which failed to be processed
254
+ # [image_download_errors] Returns error objects of files which failed to be downloaded
246
255
  #
247
256
  # [image_identifiers] Reads out the identifiers of the files
248
257
  #
@@ -329,11 +338,8 @@ module CarrierWave
329
338
  return if frozen?
330
339
  mounter = _mounter(:#{column})
331
340
 
332
- if mounter.remove?
333
- write_uploader(mounter.serialization_column, nil)
334
- elsif mounter.identifiers.any?
335
- write_uploader(mounter.serialization_column, mounter.identifiers)
336
- end
341
+ mounter.clear! if mounter.remove?
342
+ write_uploader(mounter.serialization_column, mounter.identifiers.presence)
337
343
  end
338
344
 
339
345
  def #{column}_identifiers
@@ -395,16 +401,16 @@ module CarrierWave
395
401
  _mounter(:#{column}).store!
396
402
  end
397
403
 
398
- def #{column}_integrity_error
399
- _mounter(:#{column}).integrity_error
404
+ def #{column}_integrity_errors
405
+ _mounter(:#{column}).integrity_errors
400
406
  end
401
407
 
402
- def #{column}_processing_error
403
- _mounter(:#{column}).processing_error
408
+ def #{column}_processing_errors
409
+ _mounter(:#{column}).processing_errors
404
410
  end
405
411
 
406
- def #{column}_download_error
407
- _mounter(:#{column}).download_error
412
+ def #{column}_download_errors
413
+ _mounter(:#{column}).download_errors
408
414
  end
409
415
 
410
416
  def mark_remove_#{column}_false
@@ -3,14 +3,17 @@ module CarrierWave
3
3
  # this is an internal class, used by CarrierWave::Mount so that
4
4
  # we don't pollute the model with a lot of methods.
5
5
  class Mounter #:nodoc:
6
- attr_reader :column, :record, :remote_urls, :integrity_error,
7
- :processing_error, :download_error
6
+ attr_reader :column, :record, :remote_urls, :integrity_errors,
7
+ :processing_errors, :download_errors
8
8
  attr_accessor :remove, :remote_request_headers
9
9
 
10
10
  def initialize(record, column, options={})
11
11
  @record = record
12
12
  @column = column
13
13
  @options = record.class.uploader_options[column]
14
+ @download_errors = []
15
+ @processing_errors = []
16
+ @integrity_errors = []
14
17
  end
15
18
 
16
19
  def uploader_class
@@ -38,21 +41,30 @@ module CarrierWave
38
41
  end
39
42
 
40
43
  def cache(new_files)
41
- return if not new_files or new_files == ""
44
+ return if !new_files.is_a?(Array) && new_files.blank?
45
+ old_uploaders = uploaders
42
46
  @uploaders = new_files.map do |new_file|
43
- uploader = blank_uploader
44
- uploader.cache!(new_file)
45
- uploader
46
- end
47
-
48
- @integrity_error = nil
49
- @processing_error = nil
50
- rescue CarrierWave::IntegrityError => e
51
- @integrity_error = e
52
- raise e unless option(:ignore_integrity_errors)
53
- rescue CarrierWave::ProcessingError => e
54
- @processing_error = e
55
- raise e unless option(:ignore_processing_errors)
47
+ handle_error do
48
+ if new_file.is_a?(String)
49
+ if (uploader = old_uploaders.detect { |uploader| uploader.identifier == new_file })
50
+ uploader.staged = true
51
+ uploader
52
+ else
53
+ begin
54
+ uploader = blank_uploader
55
+ uploader.retrieve_from_cache!(new_file)
56
+ uploader
57
+ rescue CarrierWave::InvalidParameter
58
+ nil
59
+ end
60
+ end
61
+ else
62
+ uploader = blank_uploader
63
+ uploader.cache!(new_file)
64
+ uploader
65
+ end
66
+ end
67
+ end.compact
56
68
  end
57
69
 
58
70
  def cache_names
@@ -60,45 +72,36 @@ module CarrierWave
60
72
  end
61
73
 
62
74
  def cache_names=(cache_names)
63
- return if not cache_names or cache_names == "" or uploaders.any?(&:cached?)
64
- @uploaders = cache_names.map do |cache_name|
65
- uploader = blank_uploader
66
- uploader.retrieve_from_cache!(cache_name)
67
- uploader
75
+ return if cache_names.blank?
76
+ clear_unstaged
77
+ cache_names.map do |cache_name|
78
+ begin
79
+ uploader = blank_uploader
80
+ uploader.retrieve_from_cache!(cache_name)
81
+ @uploaders << uploader
82
+ rescue CarrierWave::InvalidParameter
83
+ # ignore
84
+ end
68
85
  end
69
- rescue CarrierWave::InvalidParameter
70
86
  end
71
87
 
72
88
  def remote_urls=(urls)
73
- return if not urls or urls == "" or urls.all?(&:blank?)
89
+ return if urls.blank? || urls.all?(&:blank?)
74
90
 
75
91
  @remote_urls = urls
76
- @download_error = nil
77
- @integrity_error = nil
78
92
 
79
- @uploaders = urls.zip(remote_request_headers || []).map do |url, header|
80
- uploader = blank_uploader
81
- uploader.download!(url, header || {})
82
- uploader
93
+ clear_unstaged
94
+ urls.zip(remote_request_headers || []).map do |url, header|
95
+ handle_error do
96
+ uploader = blank_uploader
97
+ uploader.download!(url, header || {})
98
+ @uploaders << uploader
99
+ end
83
100
  end
84
-
85
- rescue CarrierWave::DownloadError => e
86
- @download_error = e
87
- raise e unless option(:ignore_download_errors)
88
- rescue CarrierWave::ProcessingError => e
89
- @processing_error = e
90
- raise e unless option(:ignore_processing_errors)
91
- rescue CarrierWave::IntegrityError => e
92
- @integrity_error = e
93
- raise e unless option(:ignore_integrity_errors)
94
101
  end
95
102
 
96
103
  def store!
97
- if remove?
98
- remove!
99
- else
100
- uploaders.reject(&:blank?).each(&:store!)
101
- end
104
+ uploaders.reject(&:blank?).each(&:store!)
102
105
  end
103
106
 
104
107
  def urls(*args)
@@ -110,7 +113,7 @@ module CarrierWave
110
113
  end
111
114
 
112
115
  def remove?
113
- remove.present? && (remove == true || remove !~ /\A0|false$\z/)
116
+ remove.present? && remove !~ /\A0|false$\z/
114
117
  end
115
118
 
116
119
  def remove!
@@ -118,6 +121,10 @@ module CarrierWave
118
121
  @uploaders = []
119
122
  end
120
123
 
124
+ def clear!
125
+ @uploaders = []
126
+ end
127
+
121
128
  def serialization_column
122
129
  option(:mount_on) || column
123
130
  end
@@ -146,9 +153,7 @@ module CarrierWave
146
153
  end.path
147
154
  end
148
155
  before.each do |uploader|
149
- if uploader.remove_previously_stored_files_after_update and not after_paths.include?(uploader.path)
150
- uploader.remove!
151
- end
156
+ uploader.remove! if uploader.remove_previously_stored_files_after_update && !after_paths.include?(uploader.path)
152
157
  end
153
158
  end
154
159
 
@@ -161,5 +166,22 @@ module CarrierWave
161
166
  self.uploader_options[name] ||= record.class.uploader_option(column, name)
162
167
  end
163
168
 
169
+ def clear_unstaged
170
+ @uploaders ||= []
171
+ @uploaders.keep_if(&:staged)
172
+ end
173
+
174
+ def handle_error
175
+ yield
176
+ rescue CarrierWave::DownloadError => e
177
+ @download_errors << e
178
+ raise e unless option(:ignore_download_errors)
179
+ rescue CarrierWave::ProcessingError => e
180
+ @processing_errors << e
181
+ raise e unless option(:ignore_processing_errors)
182
+ rescue CarrierWave::IntegrityError => e
183
+ @integrity_errors << e
184
+ raise e unless option(:ignore_integrity_errors)
185
+ end
164
186
  end # Mounter
165
187
  end # CarrierWave
@@ -12,7 +12,9 @@ module CarrierWave
12
12
  def mount_uploader(column, uploader=nil, options={}, &block)
13
13
  super
14
14
 
15
- class_eval <<-RUBY, __FILE__, __LINE__+1
15
+ mod = Module.new
16
+ prepend mod
17
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
16
18
  def remote_#{column}_url=(url)
17
19
  column = _mounter(:#{column}).serialization_column
18
20
  __send__(:"\#{column}_will_change!")
@@ -27,7 +29,9 @@ module CarrierWave
27
29
  def mount_uploaders(column, uploader=nil, options={}, &block)
28
30
  super
29
31
 
30
- class_eval <<-RUBY, __FILE__, __LINE__+1
32
+ mod = Module.new
33
+ prepend mod
34
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
31
35
  def remote_#{column}_urls=(url)
32
36
  column = _mounter(:#{column}).serialization_column
33
37
  __send__(:"\#{column}_will_change!")
@@ -52,15 +56,16 @@ module CarrierWave
52
56
  validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
53
57
  validates_download_of column if uploader_option(column.to_sym, :validate_download)
54
58
 
55
- after_save :"store_#{column}!"
56
59
  before_save :"write_#{column}_identifier"
60
+ after_save :"store_previous_changes_for_#{column}"
57
61
  after_commit :"remove_#{column}!", :on => :destroy
58
62
  after_commit :"mark_remove_#{column}_false", :on => :update
59
-
60
- after_save :"store_previous_changes_for_#{column}"
61
63
  after_commit :"remove_previously_stored_#{column}", :on => :update
64
+ after_commit :"store_#{column}!", :on => [:create, :update]
62
65
 
63
- class_eval <<-RUBY, __FILE__, __LINE__+1
66
+ mod = Module.new
67
+ prepend mod
68
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
64
69
  def #{column}=(new_file)
65
70
  column = _mounter(:#{column}).serialization_column
66
71
  if !(new_file.blank? && __send__(:#{column}).blank?)
@@ -72,8 +77,9 @@ module CarrierWave
72
77
 
73
78
  def remove_#{column}=(value)
74
79
  column = _mounter(:#{column}).serialization_column
75
- __send__(:"\#{column}_will_change!")
76
- super
80
+ result = super
81
+ __send__(:"\#{column}_will_change!") if _mounter(:#{column}).remove?
82
+ result
77
83
  end
78
84
 
79
85
  def remove_#{column}!