carrierwave 1.2.1 → 2.1.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7a985080c1432e2d6d27cbe46b823f4ca5bdbc75
4
- data.tar.gz: e3e60a7159815499f0559f9574480633e73d68ed
2
+ SHA256:
3
+ metadata.gz: 84145959d912145a2915ab7adb9ccbd6bf7a971ab00cef83fb7f5092c349c9c5
4
+ data.tar.gz: 9ba122388fb8ff36563001ea9144e4a1d523b7be78433fed2a971a82588de5c8
5
5
  SHA512:
6
- metadata.gz: 9e027080e1e7fcc86a2f6e0b74cb0d4db2d1f955b733c9bf47354fb74da0707c0017a9305fdc6f090825a7203b319f25bcd61ecc2fa84f123da19a9b9d784d6c
7
- data.tar.gz: 2c8d16ca3353952282e9ffb97d37d44477c5a22f4744f99a84d0fde7bc0ee9d6b456698aa386d0664e8b734f9b8edf155a8034b15efb5b60831579a610d09b67
6
+ metadata.gz: 70f718887e9f4223405d8f273cafaf429395725eead688918d454499a50063fdc748cc519440d2010f783cfa1ec3ba19f54b5e07dc644d195cbe6d59f1c5b6b2
7
+ data.tar.gz: 50907c92735e096c691a1ee7d440a2b83de736de3016b78c11796d0c558c283774236e5acf18abb552212816a38905986a6ec3ad0d956736f88c79ae99cd9aa6
data/README.md CHANGED
@@ -4,8 +4,8 @@ This gem provides a simple and extremely flexible way to upload files from Ruby
4
4
  It works well with Rack based web applications, such as Ruby on Rails.
5
5
 
6
6
  [![Build Status](https://travis-ci.org/carrierwaveuploader/carrierwave.svg?branch=master)](http://travis-ci.org/carrierwaveuploader/carrierwave)
7
- [![Code Climate](http://img.shields.io/codeclimate/github/carrierwaveuploader/carrierwave.svg)](https://codeclimate.com/github/carrierwaveuploader/carrierwave)
8
- [![git.legal](https://git.legal/projects/1363/badge.svg "Number of libraries approved")](https://git.legal/projects/1363)
7
+ [![Code Climate](https://codeclimate.com/github/carrierwaveuploader/carrierwave.svg)](https://codeclimate.com/github/carrierwaveuploader/carrierwave)
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
 
10
10
 
11
11
  ## Information
@@ -24,19 +24,19 @@ It works well with Rack based web applications, such as Ruby on Rails.
24
24
  Install the latest release:
25
25
 
26
26
  ```
27
- $ gem install carrierwave -v "1.0.0"
27
+ $ gem install carrierwave
28
28
  ```
29
29
 
30
30
  In Rails, add it to your Gemfile:
31
31
 
32
32
  ```ruby
33
- gem 'carrierwave', '~> 1.0'
33
+ gem 'carrierwave', '~> 2.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
 
@@ -89,7 +89,7 @@ a migration:
89
89
 
90
90
 
91
91
  rails g migration add_avatar_to_users avatar:string
92
- rake db:migrate
92
+ rails db:migrate
93
93
 
94
94
  Open your model file and mount the uploader:
95
95
 
@@ -144,12 +144,12 @@ example, create a migration like this:
144
144
  #### For databases with ActiveRecord json data type support (e.g. PostgreSQL, MySQL)
145
145
 
146
146
  rails g migration add_avatars_to_users avatars:json
147
- rake db:migrate
147
+ rails db:migrate
148
148
 
149
149
  #### For database without ActiveRecord json data type support (e.g. SQLite)
150
150
 
151
151
  rails g migration add_avatars_to_users avatars:string
152
- rake db:migrate
152
+ rails db:migrate
153
153
 
154
154
  __Note__: JSON datatype doesn't exists in SQLite adapter, that's why you can use a string datatype which will be serialized in model.
155
155
 
@@ -163,6 +163,9 @@ class User < ActiveRecord::Base
163
163
  end
164
164
  ```
165
165
 
166
+ Make sure that you mount the uploader with write (mount_uploaders) with `s` not (mount_uploader)
167
+ in order to avoid errors when uploading multiple files
168
+
166
169
  Make sure your file input fields are set up as multiple file fields. For
167
170
  example in Rails you'll want to do something like this:
168
171
 
@@ -187,6 +190,17 @@ u.avatars[0].current_path # => 'path/to/file.png'
187
190
  u.avatars[0].identifier # => 'file.png'
188
191
  ```
189
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
+
190
204
  ## Changing the storage directory
191
205
 
192
206
  In order to change where uploaded files are put, just override the `store_dir`
@@ -252,6 +266,22 @@ class NoJsonUploader < CarrierWave::Uploader::Base
252
266
  end
253
267
  ```
254
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
+
255
285
  ### Filenames and unicode chars
256
286
 
257
287
  Another security issue you should care for is the file names (see
@@ -277,7 +307,7 @@ You no longer need to do this manually.
277
307
 
278
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*:
279
309
 
280
- *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.
281
311
 
282
312
  Some documentation refers to RMagick instead of MiniMagick but MiniMagick is recommended.
283
313
 
@@ -301,8 +331,14 @@ end
301
331
  ```
302
332
 
303
333
  When this uploader is used, an uploaded image would be scaled to be no larger
304
- than 800 by 800 pixels. A version called thumb is then created, which is scaled
305
- and cropped to exactly 200 by 200 pixels. The uploader could be used like this:
334
+ than 800 by 800 pixels. The original aspect ratio will be kept.
335
+
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
338
+ that the width and height specified are filled, only cropping
339
+ if the aspect ratio requires it.
340
+
341
+ The above uploader could be used like this:
306
342
 
307
343
  ```ruby
308
344
  uploader = AvatarUploader.new
@@ -315,6 +351,18 @@ uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200
315
351
  One important thing to remember is that process is called *before* versions are
316
352
  created. This can cut down on processing cost.
317
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
+
318
366
  It is possible to nest versions within versions:
319
367
 
320
368
  ```ruby
@@ -351,7 +399,7 @@ private
351
399
  end
352
400
 
353
401
  def is_landscape? picture
354
- image = MiniMagick::Image.open(picture.path)
402
+ image = MiniMagick::Image.new(picture.path)
355
403
  image[:width] > image[:height]
356
404
  end
357
405
 
@@ -625,6 +673,8 @@ describe MyUploader do
625
673
  end
626
674
  ```
627
675
 
676
+ If you're looking for minitest asserts, checkout [carrierwave_asserts](https://github.com/hcfairbanks/carrierwave_asserts).
677
+
628
678
  Setting the enable_processing flag on an uploader will prevent any of the versions from processing as well.
629
679
  Processing can be enabled for a single version by setting the processing flag on the version like so:
630
680
 
@@ -638,7 +688,6 @@ If you want to use fog you must add in your CarrierWave initializer the
638
688
  following lines
639
689
 
640
690
  ```ruby
641
- config.fog_provider = 'fog' # 'fog/aws' etc. Defaults to 'fog'
642
691
  config.fog_credentials = { ... } # Provider specific credentials
643
692
  ```
644
693
 
@@ -656,18 +705,18 @@ You can also pass in additional options, as documented fully in lib/carrierwave/
656
705
 
657
706
  ```ruby
658
707
  CarrierWave.configure do |config|
659
- config.fog_provider = 'fog/aws' # required
660
708
  config.fog_credentials = {
661
709
  provider: 'AWS', # required
662
- aws_access_key_id: 'xxx', # required
663
- aws_secret_access_key: 'yyy', # required
710
+ aws_access_key_id: 'xxx', # required unless using use_iam_profile
711
+ aws_secret_access_key: 'yyy', # required unless using use_iam_profile
712
+ use_iam_profile: true, # optional, defaults to false
664
713
  region: 'eu-west-1', # optional, defaults to 'us-east-1'
665
714
  host: 's3.example.com', # optional, defaults to nil
666
715
  endpoint: 'https://s3.example.com:8080' # optional, defaults to nil
667
716
  }
668
- config.fog_directory = 'name_of_directory' # required
669
- config.fog_public = false # optional, defaults to true
670
- config.fog_attributes = { cache_control: "public, max-age=#{365.day.to_i}" } # optional, defaults to {}
717
+ config.fog_directory = 'name_of_bucket' # required
718
+ config.fog_public = false # optional, defaults to true
719
+ config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
671
720
  end
672
721
  ```
673
722
 
@@ -681,6 +730,14 @@ end
681
730
 
682
731
  That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Amazon S3.
683
732
 
733
+ **Note**: for Carrierwave to work properly it needs credentials with the following permissions:
734
+
735
+ * `s3:ListBucket`
736
+ * `s3:PutObject`
737
+ * `s3:GetObject`
738
+ * `s3:DeleteObject`
739
+ * `s3:PutObjectAcl`
740
+
684
741
  ## Using Rackspace Cloud Files
685
742
 
686
743
  [Fog](http://github.com/fog/fog) is used to support Rackspace Cloud Files. Ensure you have it in your Gemfile:
@@ -696,7 +753,6 @@ Using a US-based account:
696
753
 
697
754
  ```ruby
698
755
  CarrierWave.configure do |config|
699
- config.fog_provider = "fog/rackspace/storage" # optional, defaults to "fog"
700
756
  config.fog_credentials = {
701
757
  provider: 'Rackspace',
702
758
  rackspace_username: 'xxxxxx',
@@ -711,7 +767,6 @@ Using a UK-based account:
711
767
 
712
768
  ```ruby
713
769
  CarrierWave.configure do |config|
714
- config.fog_provider = "fog/rackspace/storage" # optional, defaults to "fog"
715
770
  config.fog_credentials = {
716
771
  provider: 'Rackspace',
717
772
  rackspace_username: 'xxxxxx',
@@ -760,7 +815,6 @@ Please read the [fog-google README](https://github.com/fog/fog-google/blob/maste
760
815
 
761
816
  ```ruby
762
817
  CarrierWave.configure do |config|
763
- config.fog_provider = 'fog/google' # required
764
818
  config.fog_credentials = {
765
819
  provider: 'Google',
766
820
  google_storage_access_key_id: 'xxxxxx',
@@ -857,8 +911,8 @@ manipulation methods.
857
911
 
858
912
  ## Using MiniMagick
859
913
 
860
- MiniMagick is similar to RMagick but performs all the operations using the 'mogrify'
861
- command which is part of the standard ImageMagick kit. This allows you to have the power
914
+ MiniMagick is similar to RMagick but performs all the operations using the 'convert'
915
+ CLI which is part of the standard ImageMagick kit. This allows you to have the power
862
916
  of ImageMagick without having to worry about installing all the RMagick libraries.
863
917
 
864
918
  See the MiniMagick site for more details:
@@ -912,7 +966,7 @@ errors:
912
966
  carrierwave_download_error: could not be downloaded
913
967
  extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
914
968
  extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
915
- content_type_whitelist_error: "You are not allowed to upload %{content_type} files"
969
+ content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
916
970
  content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
917
971
  rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
918
972
  mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
@@ -0,0 +1,83 @@
1
+ require 'open-uri'
2
+ require 'ssrf_filter'
3
+ require 'addressable'
4
+ require 'carrierwave/downloader/remote_file'
5
+
6
+ module CarrierWave
7
+ module Downloader
8
+ class Base
9
+ attr_reader :uploader
10
+
11
+ def initialize(uploader)
12
+ @uploader = uploader
13
+ end
14
+
15
+ ##
16
+ # Downloads a file from given URL and returns a RemoteFile.
17
+ #
18
+ # === Parameters
19
+ #
20
+ # [url (String)] The URL where the remote file is stored
21
+ # [remote_headers (Hash)] Request headers
22
+ #
23
+ def download(url, remote_headers = {})
24
+ headers = remote_headers.
25
+ reverse_merge('User-Agent' => "CarrierWave/#{CarrierWave::VERSION}")
26
+ uri = process_uri(url.to_s)
27
+ begin
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
38
+ rescue StandardError => e
39
+ raise CarrierWave::DownloadError, "could not download file: #{e.message}"
40
+ end
41
+ CarrierWave::Downloader::RemoteFile.new(response)
42
+ end
43
+
44
+ ##
45
+ # Processes the given URL by parsing and escaping it. Public to allow overriding.
46
+ #
47
+ # === Parameters
48
+ #
49
+ # [url (String)] The URL where the remote file is stored
50
+ #
51
+ def process_uri(uri)
52
+ uri_parts = uri.split('?')
53
+ encoded_uri = Addressable::URI.parse(uri_parts.shift).normalize.to_s
54
+ encoded_uri << '?' << Addressable::URI.encode(uri_parts.join('?')).gsub('%5B', '[').gsub('%5D', ']') if uri_parts.any?
55
+ URI.parse(encoded_uri)
56
+ rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
57
+ raise CarrierWave::DownloadError, "couldn't parse URL: #{uri}"
58
+ end
59
+
60
+ ##
61
+ # If this returns true, SSRF protection will be bypassed.
62
+ # You can override this if you want to allow accessing specific local URIs that are not SSRF exploitable.
63
+ #
64
+ # === Parameters
65
+ #
66
+ # [uri (URI)] The URI where the remote file is stored
67
+ #
68
+ # === Examples
69
+ #
70
+ # class CarrierWave::Downloader::CustomDownloader < CarrierWave::Downloader::Base
71
+ # def skip_ssrf_protection?(uri)
72
+ # uri.hostname == 'localhost' && uri.port == 80
73
+ # end
74
+ # end
75
+ #
76
+ # my_uploader.downloader = CarrierWave::Downloader::CustomDownloader
77
+ #
78
+ def skip_ssrf_protection?(uri)
79
+ false
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,65 @@
1
+ module CarrierWave
2
+ module Downloader
3
+ class RemoteFile
4
+ attr_reader :file, :uri
5
+
6
+ def initialize(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 || {}
29
+ end
30
+
31
+ def original_filename
32
+ filename = filename_from_header || filename_from_uri
33
+ mime_type = MiniMime.lookup_by_content_type(content_type)
34
+ unless File.extname(filename).present? || mime_type.blank?
35
+ filename = "#{filename}.#{mime_type.extension}"
36
+ end
37
+ filename
38
+ end
39
+
40
+ def respond_to?(*args)
41
+ super || file.respond_to?(*args)
42
+ end
43
+
44
+ private
45
+
46
+ def filename_from_header
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
53
+ end
54
+
55
+ def filename_from_uri
56
+ CGI.unescape(File.basename(uri.path))
57
+ end
58
+
59
+ def method_missing(*args, &block)
60
+ file.send(*args, &block)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -6,7 +6,7 @@ en:
6
6
  carrierwave_download_error: could not be downloaded
7
7
  extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
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"
9
+ content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
10
10
  content_type_blacklist_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}"
@@ -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