Server side validation of file size - ruby-on-rails

I am trying to find a way to limit the size of files that users can upload, and it seems to be possible with javascript, but what bothers me is that what happens if a user just turns javascript off and upload like 1GB file to the server, and I did find a way to validate it on server side but it takes place once the file has been uploaded, which isnt an option. So I am trying to find the right way to do it but the best thing I came up with is to create a form for file uploading with javascript and make validations on client side but I guess there is still a way around it...
I am using RoR and CarrierWave if that matters ...
EDIT
1)Everything I can find on this page is to use size validation which happens after the file has been uploaded
2) I use that validation by carrierwave but again, it takes place once the file has been uploaded, it doesnt make sense to wait an hour till some huge file gets uploaded just to know its too big

You can use a Rails custom validator to verify your attachment meets specific file size requirements.
Grab a copy of the validator from https://gist.github.com/1009861 and save it to your lib/ folder as file_size_validator.rb. Add the error translations to config/locales/en.yml or wherever is appropriate for your setup. Then do this in your parent model:
# app/models/brand.rb
require 'file_size_validator'
class Brand < ActiveRecord::Base
mount_uploader :logo, BrandLogoUploader
validates :logo,
:presence => true,
:file_size => {
:maximum => 0.5.megabytes.to_i
}
end
Like validates_length_of, validates_file_size accepts :maximum, :minimum, :in [range], and :is options.
Another solution
A custom validator could also be used like this.
app/models/user.rb
class User< ActiveRecord::Base
attr_accessible :product_upload_limit
has_many :products
end
app/models/brand.rb
class Product < ActiveRecord::Base
mount_uploader :file, FileUploader
belongs_to :user
validate :file_size
def file_size
if file.file.size.to_f/(1000*1000) > user.product_upload_limit.to_f
errors.add(:file, "You cannot upload a file greater than #{upload_limit.to_f}MB")
end
end
end
Here, the upload limit varies from user to user & is saved in the user model.

The problem was solved by nginx, which works like a buffer so if somebody is trying to upload a 1GB file, it will go to your buffer first so it won't block your unicorn instance until it's completely uploaded, so that's one good thing. The other is that you can set the max content-length parameter in your nginx config file, so if you set to 5 Megabytes and somebody is trying to upload 1 GB file, it'll be denied. But again, I am not sure how it works, if it just checks the http header, then I have a feeling that somebody can simply tamper with this value and fool your nginx.

Related

CarrierWave gem isn't uploading, so why is there no raised exception?

I'm attempting to install CarrierWave gem on ActiveAdmin in rails, and the setup seemed easy enough. However, when I attempt to upload a test image to the /public/uploads directory, the image isn't saved. What's more irritating is the fact that there is no exception being raised, so I don't know where to look in order to find the issue. I can create a post, browse for an image, select that image, and submit the post in order to be saved, but I still end up with IMAGE: EMPTY on the show page in ActiveAdmin as shown below. In the image, I wrote a lorem ipsum post that included an image, and I saved it.
How to I actually get the uploader to upload?
ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin10.8.0]
Rails 4.1.6
This is the show page for a single Post object within ActiveAdmin
This is a full page screenshot of the form in question
The same form, but zoomed in. Obviously, I wasn't trying to upload an image in this screenshot.
/app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"public/uploads"
end
end
/app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :category
scope :rails, -> { where(category_id: 1) }
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
mount_uploader :image, ImageUploader
end
After a couple more hours of searching, I found the inspiration for an answer in another question, which you can find here.
The reason I wasn't getting a result at all was because I had not permitted the application to accept an :image in the app/admin/post.rb file. Now, I still don't know why there was no error raised (or even a line logged to the console), but fixing the problem of getting an upload is as simple as permitting the upload parameter as follows:
permit_params :title, :slug, :blurb, :content, :category_id, :image
When I posted this question, I had not added in that last :image, so my application was simply throwing away that parameter instead of saving it within the file system with the rest of the post.

Rails: using sidekiq to asynchronously create records that mount a carrierwave uploader

I'm trying to take the creation of associated images (PostPhoto) for a Post (both the creation of the record and the upload and processing) and move them to a background process via SideKiq. Here are the models:
class Post < ActiveRecord::Base
has_many :post_photos
accepts_nested_attributes_for :post_photos, allow_destroy: true
class PostPhoto < ActiveRecord::Base
mount_uploader :photo, PhotoUploader
belongs_to :post
Here's my uploader:
class PhotoUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :fog
include CarrierWave::MimeTypes
process :set_content_type
Originally, after the post got saved, I ran this in my posts controller:
params[:post_photos]['photo'].each do |p|
#post.post_photos.create!(:photo => p, :post_id => #post.id)
end
This worked fine. Now I'm trying to do this:
params[:post_photos]['photo'].each do |p|
PostPhotoWorker.perform_async p, #post.id
end
And here's my PostPhotoWorker
class PostPhotoWorker
include Sidekiq::Worker
sidekiq_options queue: "high"
sidekiq_options retry: false
def perform(p, post_id)
post = Post.find_by_id(post_id);
post.post_photos.create!(:photo => p, :post_id => post_id)
end
end
The job is failing. In the sidekiq log I'm seeing:
2014-08-21T12:23:09.583Z 24655 TID-ovhl6hlbw WARN: no implicit conversion of nil into String
I know that both the post_id and the photo temp file p are being passed correctly, and that the post = Post.find_by_id(post_id); is finding the post. But it would seem that something is failing in the create action. Is there some reason why the creation of the associated photo records along with upload and processing would fail in a background process but succeed as part of the primary process? There's no stack trace as part of the sidekiq logs, so I can't figure out where this issue is arising.
Any idea what's causing this?
FYI
I can't use Carrierwave Backgrounder because I'm on Heroku for production, and Carrierwave Backgrounder doesn't support background storing of images on Heroku because of the Ephemeral File System (which is a major part of why I want background processing in the first place). I don't want to do Carrierwave Direct because it seems to require separate forms for the post and the images, and I'm not ready to refactor my code to support that, at least not until I can figure out a way to do it elegantly.
I have a feeling that passing the entire photo temp file as a value to redis is A Bad Idea. But looking through the documentation for redis, it seems like it should support it.
First off use carrierwave direct. It uploads images straight to S3 or the like from the web client. This gets around all of herokus issues with file uploads, if it be the temporary file structure, or how it can timeout with large file uploads.
https://github.com/dwilkie/carrierwave_direct
You can follow there normal set up, but instead of saving the detail to the actually model you will either have the upload information put into a temporary attribute and then after_commit have that information used for creating a background task, or in the controller action create the background task.
PhotoProcessorWorker.perform_async(params[:key], #object.id)
And then the actually logic. You get the object, you set the upload key, by doing remote_photo_url you force the object to be downloaded, and when you save the processing of the photo happens happens.
class PhotoProcessorWorker
include Sidekiq::Worker
sidekiq_options :queue => :default
def perform(key, obj_id)
obj = Object.where(:id => org_id).first
obj.photo.key = key
obj.remote_photo_url = obj.photo.direct_fog_url(:with_path => true)
obj.save
end
end
Edit: Sorry mist the FYI part this is how you could do it for multiple, it would require more than one request but it should work.
#post.post_photos.each do |pp|
= direct_upload_form_for pp.photo, :remote => true, :class => "photo_upload" do |f|
.avatar.form-group
= f.label :photo
= f.file_field :photo, :class => "btn btn-default has-image-preview"
#submit_all_images.button_class
With this you would then have a piece of javascript that would submit each form after #submit_all_images was clicked. Each upload would happen separate, but which would be fine because the only work that would happen in the foreground would be the queueing of the background task.
Also if your creating the post at the same time, you could make this work by making the post form remote, returning the id after successful creation, and then submitting all the photo uploads the same way as above after confirmation.

Carrierwave: How to validate that image is attached? (either upload, or remote_url)

I'm trying to make sure that every instance of Picture model has a file attached.
In other words - there are two fields in the form:
:file
:remote_file_url
I want user to fill in at least one of them.
When I validate presence of :file, and submit remote_file_url instead of file - then it gives validation error.
The only way I found for now is do smth like this:
class Picture < ActiveRecord::Base
validate :file_xor_remote_file_url
private
def file_xor_remote_file_url
if !(file.blank? ^ remote_file_url.blank?)
errors.add(:base, "Specify a file to upload, or file URL, not both")
end
end
end
The wiki says you can validate your upload like this:
mount_uploader :avatar, AvatarUploader
validates_presence_of :avatar
It doesn't mention any different handling for remote URLs, but underlines that Carrierwave validates the presence of the file rather than just the presence of an URL. So a given remote URL must refer to a valid file that can be uploaded.
You said that "validate presence of :file", but perhaps this could be the point. Hope this helps.

How to make localized Paperclip attachments with Globalize3?

I have a project using Paperclip gem for attachments and Globalize3 for attribute translation. Records need to have a different attachment for each locale.
I though about moving Paperclip attributes to translation table, and that might work, but I don't think that would work when Paperclip needs to delete attachments.
What's the best way to achieve something like that?
UPDATE: to be clear, I want this because my client wants to upload different images for each locale.
Unfortunately I didn't find a way to do this using Globalize3. In theory, I could have added a separate model for image and add image_id to list of translated columns (to have something like MainModel -> Translation -> Image), but it seems that Globalize has some migration issues with non-string columns.
Instead of using Globalize3, I did this with a separate Image model with locale attribute and main model which accepts nested attributes for it. Something along the lines of:
class MainModel < ActiveRecord::Base
has_many :main_model_images
accepts_nested_attributes_for :main_model_images
# return image for locale or any other as a fallback
def localized_image(locale)
promo_box_images.where(:locale => locale).first || promo_box_images.first
end
end
class MainModelImage < ActiveRecord::Base
belongs_to :main_model
has_attached_file :image
validates :locale,
:presence => true,
:uniqueness => { :scope => :main_model_id }
end
Tricky part was getting form to accept nested attributes only for one image, instead of all images in has_many relation.
=f.fields_for :main_model_images, #main_model.image_for_locale(I18n.locale) do |f_image|
=f_image.hidden_field :locale
=f_image.label :image
You could also try the paperclip-globalize3 gem, it should handle the case you describe. https://github.com/emjot/paperclip-globalize3
Ok since you asked me to share my solution to this problem even though I am using Carrierwave as a library for uploading here is it:
Ok so I would have a model setup like this:
class MyModel < ActiveRecord::Base
# ...
translates :attr_one, :attr_two, :uploaded_file
Now what I need for CarrierWave to work is place to attach the uploader to and that can be done on the Translation model
Translation.mount_uploader :uploaded_file, FileUploader
end
Now for your question about deleting, I think though I haven't needed to do it but it should work as the README says it should but on the translation model. https://github.com/jnicklas/carrierwave#removing-uploaded-files
MyModel.first.translation.remove_uploaded_file!
I haven't taken a look at paperclip for a good 2 years and if this is not applicable knowledge I suggest you try out carrierwave.

Rails carrierwave mounting on condition

I need to mount picture uploader after some verification function.
But if I call as usual mounting uploader in model:
mount_uploader :content, ContentUploader
carrierwave first download content, and then Rails start verification of model.
Specifically, I don't want to load big files at all! I want to check http header Content-length and Content-type and then, if it's OK, mount uploader.
Maybe something like that:
if condition
mount_uploader :content, ContentUploader
end
How can I do it?
P.S. Rails version 3.2.12
This is not the way to go if you just want avoid loading big files! That said, one can have conditional mount overriding content=.
As CarrierWave v1.1.0 there is still no conditional mount. But note that mount_uploader first includes a module in the class and then overrides the original content= to call the method content= defined in the included module. So, a workaround is just to redefine the accessors after you have called mount_uploader:
class YourModel < ActiveRecord::Base
mount_uploader :content, ContentUploader
def content=(arg)
if condition
super
else
# original behavior
write_attribute(:content, arg)
end
end
def content
# some logic here
end
end

Resources