├── spec ├── fixtures │ ├── case.JPG │ ├── new.jpeg │ ├── old.jpeg │ ├── test+.jpg │ ├── test.jpeg │ ├── test.jpg │ ├── Uppercase.jpg │ ├── sponsored.doc │ ├── ruby.gif │ ├── ruby.png │ ├── landscape.jpg │ ├── multi_page.pdf │ ├── portrait.jpg │ ├── bork.txt │ ├── new.txt │ ├── old.txt │ ├── bork.ttxt │ └── bork.txtt ├── uploader │ ├── paths_spec.rb │ ├── mountable_spec.rb │ ├── callback_spec.rb │ ├── magic_mime_blacklist_spec.rb │ ├── magic_mime_whitelist_spec.rb │ ├── file_size_spec.rb │ ├── proxy_spec.rb │ ├── remove_spec.rb │ ├── default_url_spec.rb │ ├── overrides_spec.rb │ ├── extension_whitelist_spec.rb │ ├── extension_blacklist_spec.rb │ ├── configuration_spec.rb │ └── processing_spec.rb ├── generators │ └── uploader_generator_spec.rb ├── support │ └── activerecord.rb ├── storage │ ├── fog_spec.rb │ ├── fog_credentials.rb │ └── file_spec.rb ├── processing │ ├── magic_mime_types_spec.rb │ ├── mime_types_spec.rb │ └── mini_magick_spec.rb ├── spec_helper.rb └── compatibility │ └── paperclip_spec.rb ├── features ├── fixtures │ ├── bork.txt │ ├── monkey.txt │ └── upcased_bork.txt ├── step_definitions │ ├── download_steps.rb │ ├── caching_steps.rb │ ├── store_steps.rb │ ├── activerecord_steps.rb │ ├── mount_steps.rb │ ├── datamapper_steps.rb │ ├── file_steps.rb │ └── general_steps.rb ├── support │ ├── activerecord.rb │ └── env.rb ├── download.feature ├── caching.feature ├── file_storage.feature ├── file_storage_overridden_filename.feature ├── file_storage_overridden_store_dir.feature ├── versions_caching_from_versions.feature ├── mount_activerecord.feature ├── file_storage_reversing_processor.feature ├── versions_overriden_store_dir.feature ├── versions_basics.feature ├── versions_overridden_filename.feature └── versions_nested_versions.feature ├── Gemfile ├── lib ├── carrierwave │ ├── version.rb │ ├── storage.rb │ ├── utilities.rb │ ├── processing.rb │ ├── error.rb │ ├── uploader │ │ ├── default_url.rb │ │ ├── remove.rb │ │ ├── serialization.rb │ │ ├── callbacks.rb │ │ ├── url.rb │ │ ├── file_size.rb │ │ ├── mountable.rb │ │ ├── extension_blacklist.rb │ │ ├── extension_whitelist.rb │ │ ├── proxy.rb │ │ ├── processing.rb │ │ ├── magic_mime_whitelist.rb │ │ ├── magic_mime_blacklist.rb │ │ ├── store.rb │ │ ├── download.rb │ │ ├── cache.rb │ │ └── configuration.rb │ ├── utilities │ │ ├── deprecation.rb │ │ └── uri.rb │ ├── locale │ │ ├── zh-CN.yml │ │ ├── ja.yml │ │ ├── cs.yml │ │ ├── sk.yml │ │ ├── nb.yml │ │ ├── tr.yml │ │ ├── ru.yml │ │ ├── pt-BR.yml │ │ ├── de.yml │ │ ├── es.yml │ │ ├── pl.yml │ │ ├── nl.yml │ │ ├── fr-CA.yml │ │ ├── pt-PT.yml │ │ ├── en.yml │ │ ├── el.yml │ │ └── fr.yml │ ├── storage │ │ ├── abstract.rb │ │ └── file.rb │ ├── processing │ │ ├── magic_mime_types.rb │ │ └── mime_types.rb │ ├── uploader.rb │ ├── validations │ │ └── active_model.rb │ ├── orm │ │ └── activerecord.rb │ ├── compatibility │ │ └── paperclip.rb │ └── mounter.rb ├── generators │ ├── uploader_generator.rb │ └── templates │ │ └── uploader.rb └── carrierwave.rb ├── cucumber.yml ├── gemfiles ├── rails-4-0-stable.gemfile ├── rails-4-1-stable.gemfile ├── rails-4-2-stable.gemfile └── rails-master.gemfile ├── .gitignore ├── script ├── destroy ├── generate └── console ├── Rakefile ├── .travis.yml ├── CONTRIBUTING.md └── carrierwave.gemspec /spec/fixtures/case.JPG: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/new.jpeg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/old.jpeg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/test+.jpg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/test.jpeg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/test.jpg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /features/fixtures/bork.txt: -------------------------------------------------------------------------------- 1 | this is a file -------------------------------------------------------------------------------- /spec/fixtures/Uppercase.jpg: -------------------------------------------------------------------------------- 1 | this is stuff -------------------------------------------------------------------------------- /spec/fixtures/sponsored.doc: -------------------------------------------------------------------------------- 1 | Hi there 2 | -------------------------------------------------------------------------------- /features/fixtures/monkey.txt: -------------------------------------------------------------------------------- 1 | this is another file -------------------------------------------------------------------------------- /features/fixtures/upcased_bork.txt: -------------------------------------------------------------------------------- 1 | THIS IS A FILE -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/carrierwave/version.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | VERSION = "0.10.0" 3 | end 4 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: --format pretty --no-source 2 | html: --format html --out features.html -------------------------------------------------------------------------------- /spec/fixtures/ruby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augment/carrierwave/master/spec/fixtures/ruby.gif -------------------------------------------------------------------------------- /spec/fixtures/ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augment/carrierwave/master/spec/fixtures/ruby.png -------------------------------------------------------------------------------- /lib/carrierwave/storage.rb: -------------------------------------------------------------------------------- 1 | require "carrierwave/storage/abstract" 2 | require "carrierwave/storage/file" 3 | -------------------------------------------------------------------------------- /spec/fixtures/landscape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augment/carrierwave/master/spec/fixtures/landscape.jpg -------------------------------------------------------------------------------- /spec/fixtures/multi_page.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augment/carrierwave/master/spec/fixtures/multi_page.pdf -------------------------------------------------------------------------------- /spec/fixtures/portrait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/augment/carrierwave/master/spec/fixtures/portrait.jpg -------------------------------------------------------------------------------- /gemfiles/rails-4-0-stable.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", :github => "rails/rails", :branch => "4-0-stable" 4 | 5 | gemspec :path => "../" 6 | -------------------------------------------------------------------------------- /gemfiles/rails-4-1-stable.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", :github => "rails/rails", :branch => "4-1-stable" 4 | 5 | gemspec :path => "../" 6 | -------------------------------------------------------------------------------- /gemfiles/rails-4-2-stable.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", :github => "rails/rails", :branch => "4-2-stable" 4 | 5 | gemspec :path => "../" 6 | -------------------------------------------------------------------------------- /lib/carrierwave/utilities.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'carrierwave/utilities/uri' 4 | require 'carrierwave/utilities/deprecation' 5 | 6 | module CarrierWave 7 | module Utilities 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /gemfiles/rails-master.gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rails", :github => "rails/rails", :branch => "master" 4 | gem "arel", :github => "rails/arel", :branch => "master" 5 | 6 | gemspec :path => "../" 7 | -------------------------------------------------------------------------------- /lib/carrierwave/processing.rb: -------------------------------------------------------------------------------- 1 | require "carrierwave/processing/rmagick" 2 | require "carrierwave/processing/mini_magick" 3 | require "carrierwave/processing/mime_types" 4 | require "carrierwave/processing/magic_mime_types" 5 | -------------------------------------------------------------------------------- /lib/carrierwave/error.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | class UploadError < StandardError; end 3 | class IntegrityError < UploadError; end 4 | class InvalidParameter < UploadError; end 5 | class ProcessingError < UploadError; end 6 | class DownloadError < UploadError; end 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | .yardoc 3 | .DS_Store 4 | spec/public 5 | pkg 6 | doc 7 | more/activerecord/spec/db 8 | more/activerecord/spec/public 9 | more/datamapper/spec/public 10 | *.project 11 | spec/test.log 12 | spec/tmp 13 | *.swp 14 | .rvmrc 15 | .bundle 16 | Gemfile.lock 17 | gemfiles/*.lock 18 | -------------------------------------------------------------------------------- /lib/generators/uploader_generator.rb: -------------------------------------------------------------------------------- 1 | class UploaderGenerator < Rails::Generators::NamedBase 2 | source_root File.expand_path("../templates", __FILE__) 3 | 4 | def create_uploader_file 5 | template "uploader.rb", File.join('app/uploaders', class_path, "#{file_name}_uploader.rb") 6 | end 7 | end -------------------------------------------------------------------------------- /features/step_definitions/download_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I download the file '([^']+)'/ do |url| 2 | unless ENV['REMOTE'] == 'true' 3 | sham_rack_app = ShamRack.at('s3.amazonaws.com').stub 4 | sham_rack_app.register_resource('/Monkey/testfile.txt', 'S3 Remote File', 'text/plain') 5 | end 6 | 7 | @uploader.download!(url) 8 | 9 | unless ENV['REMOTE'] == 'true' 10 | ShamRack.unmount_all 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/destroy' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')) 3 | 4 | begin 5 | require 'rubigen' 6 | rescue LoadError 7 | require 'rubygems' 8 | require 'rubigen' 9 | end 10 | require 'rubigen/scripts/generate' 11 | 12 | ARGV.shift if ['--help', '-h'].include?(ARGV[0]) 13 | RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit] 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /spec/fixtures/bork.txt: -------------------------------------------------------------------------------- 1 | bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -------------------------------------------------------------------------------- /spec/fixtures/new.txt: -------------------------------------------------------------------------------- 1 | bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -------------------------------------------------------------------------------- /spec/fixtures/old.txt: -------------------------------------------------------------------------------- 1 | bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -------------------------------------------------------------------------------- /spec/fixtures/bork.ttxt: -------------------------------------------------------------------------------- 1 | bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -------------------------------------------------------------------------------- /spec/fixtures/bork.txtt: -------------------------------------------------------------------------------- 1 | bork bork bork Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -------------------------------------------------------------------------------- /features/support/activerecord.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'carrierwave/mount' 4 | require File.join(File.dirname(__FILE__), '..', '..', 'spec', 'support', 'activerecord') 5 | 6 | class TestMigration < ActiveRecord::Migration 7 | def self.up 8 | create_table :users, :force => true do |t| 9 | t.column :avatar, :string 10 | end 11 | end 12 | 13 | def self.down 14 | drop_table :users 15 | end 16 | end 17 | 18 | Before do 19 | TestMigration.up 20 | end 21 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # File: script/console 3 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 4 | 5 | libs = " -r irb/completion" 6 | # Perhaps use a console_lib to store any extra methods I may want available in the cosole 7 | # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}" 8 | libs << " -r #{File.dirname(__FILE__) + '/../lib/carrierwave.rb'}" 9 | puts "Loading carrierwave gem" 10 | exec "#{irb} #{libs} --simple-prompt" -------------------------------------------------------------------------------- /features/step_definitions/caching_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Given /^the file '(.*?)' is cached file at '(.*?)'$/ do |file, cached| 4 | FileUtils.mkdir_p(File.dirname(file_path(cached))) 5 | FileUtils.cp(file_path(file), file_path(cached)) 6 | end 7 | 8 | When /^I cache the file '(.*?)'$/ do |file| 9 | @uploader.cache!(File.open(file_path(file))) 10 | end 11 | 12 | When /^I retrieve the cache name '(.*?)' from the cache$/ do |name| 13 | @uploader.retrieve_from_cache!(name) 14 | end -------------------------------------------------------------------------------- /lib/carrierwave/uploader/default_url.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module DefaultUrl 6 | 7 | def url(*args) 8 | super || default_url(*args) 9 | end 10 | 11 | ## 12 | # Override this method in your uploader to provide a default url 13 | # in case no file has been cached/stored yet. 14 | # 15 | def default_url(*args); end 16 | 17 | end # DefaultPath 18 | end # Uploader 19 | end # CarrierWave 20 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | $:.unshift File.expand_path(File.join('..', '..', 'lib'), File.dirname(__FILE__)) 4 | 5 | require File.join(File.dirname(__FILE__), 'activerecord') 6 | 7 | require 'rspec' 8 | require 'carrierwave' 9 | require 'sham_rack' 10 | 11 | alias :running :lambda 12 | 13 | def file_path( *paths ) 14 | File.expand_path(File.join('..', *paths), File.dirname(__FILE__)) 15 | end 16 | 17 | CarrierWave.root = file_path('public') 18 | 19 | After do 20 | FileUtils.rm_rf(file_path("public")) 21 | end 22 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/remove.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Remove 6 | extend ActiveSupport::Concern 7 | 8 | include CarrierWave::Uploader::Callbacks 9 | 10 | ## 11 | # Removes the file and reset it 12 | # 13 | def remove! 14 | with_callbacks(:remove) do 15 | @file.delete if @file 16 | @file = nil 17 | @cache_id = nil 18 | end 19 | end 20 | 21 | end # Remove 22 | end # Uploader 23 | end # CarrierWave 24 | -------------------------------------------------------------------------------- /features/step_definitions/store_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Given /^the file '(.*?)' is stored at '(.*?)'$/ do |file, stored| 4 | FileUtils.mkdir_p(File.dirname(file_path(stored))) 5 | FileUtils.cp(file_path(file), file_path(stored)) 6 | end 7 | 8 | When /^I store the file$/ do 9 | @uploader.store! 10 | end 11 | 12 | When /^I store the file '(.*?)'$/ do |file| 13 | @uploader.store!(File.open(file_path(file))) 14 | end 15 | 16 | When /^I retrieve the file '(.*?)' from the store$/ do |identifier| 17 | @uploader.retrieve_from_store!(identifier) 18 | end 19 | -------------------------------------------------------------------------------- /lib/carrierwave/utilities/deprecation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'active_support/deprecation' 3 | 4 | module CarrierWave 5 | module Utilities 6 | module Deprecation 7 | 8 | def self.new version = '0.11.0', message = 'Carrierwave' 9 | if ActiveSupport::VERSION::MAJOR < 4 10 | ActiveSupport::Deprecation.warn("#{message} (will be removed from version #{version})") 11 | else 12 | ActiveSupport::Deprecation.new(version, message) 13 | end 14 | end 15 | 16 | end # Deprecation 17 | end # Utilities 18 | end # CarrierWave 19 | -------------------------------------------------------------------------------- /features/step_definitions/activerecord_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Given /^an activerecord class that uses the '([^\']*)' table$/ do |name| 4 | @mountee_klass = Class.new(ActiveRecord::Base) 5 | @mountee_klass.table_name = name 6 | end 7 | 8 | Given /^an instance of the activerecord class$/ do 9 | @instance = @mountee_klass.new 10 | end 11 | 12 | When /^I save the active record$/ do 13 | @instance.save! 14 | end 15 | 16 | When /^I reload the active record$/ do 17 | @instance = @instance.class.find(@instance.id) 18 | end 19 | 20 | When /^I delete the active record$/ do 21 | @instance.destroy 22 | end -------------------------------------------------------------------------------- /lib/carrierwave/utilities/uri.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Utilities 5 | module Uri 6 | 7 | private 8 | def encode_path(path) 9 | # based on Ruby < 2.0's URI.encode 10 | safe_string = URI::REGEXP::PATTERN::UNRESERVED + '\/' 11 | unsafe = Regexp.new("[^#{safe_string}]", false) 12 | 13 | path.to_s.gsub(unsafe) do 14 | us = $& 15 | tmp = '' 16 | us.each_byte do |uc| 17 | tmp << sprintf('%%%02X', uc) 18 | end 19 | tmp 20 | end 21 | end 22 | end # Uri 23 | end # Utilities 24 | end # CarrierWave 25 | -------------------------------------------------------------------------------- /spec/uploader/paths_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @root = CarrierWave.root 9 | CarrierWave.root = nil 10 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 11 | @uploader = @uploader_class.new 12 | end 13 | 14 | after do 15 | FileUtils.rm_rf(public_path) 16 | CarrierWave.root = @root 17 | end 18 | 19 | describe '#root' do 20 | it "should default to the current value of CarrierWave.root" do 21 | expect(@uploader.root).to be_nil 22 | CarrierWave.root = public_path 23 | expect(@uploader.root).to eq(public_path) 24 | end 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'rubygems' 3 | begin 4 | require 'bundler/setup' 5 | rescue LoadError 6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | require 'bundler' 10 | Bundler::GemHelper.install_tasks 11 | 12 | require 'rake' 13 | require 'rspec/core/rake_task' 14 | require 'cucumber' 15 | require 'cucumber/rake/task' 16 | 17 | desc "Run all examples" 18 | RSpec::Core::RakeTask.new(:spec) do |t| 19 | t.rspec_opts = %w[--color] 20 | end 21 | 22 | desc "Run cucumber features" 23 | Cucumber::Rake::Task.new(:features) do |t| 24 | t.cucumber_opts = "features --format progress" 25 | end 26 | 27 | task :default => [:spec, :features] 28 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/zh-CN.yml: -------------------------------------------------------------------------------- 1 | zh-CN: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: 处理错误 5 | carrierwave_integrity_error: 文件类型不正确 6 | carrierwave_download_error: 下载错误 7 | extension_white_list_error: "不可上传 %{extension} 文件, 可上传文件类型为: %{allowed_types}" 8 | extension_black_list_error: "不可上传 %{extension} 文件, 不可上传文件类型为: %{prohibited_types}" 9 | rmagick_processing_error: "rmagick 错误, 也许上传的文件不是图片? 原错误: %{e}" 10 | mime_types_processing_error: "MIME::Types 错误, 也许上传的文件不是正确的content-type? 原错误: %{e}" 11 | mini_magick_processing_error: "MiniMagick 错误, 也许上传的文件不是图片? 原错误: %{e}" 12 | min_size_error: "文件大小应大于 %{min_size}" 13 | max_size_error: "文件的大小应小于 %{max_size}" 14 | -------------------------------------------------------------------------------- /features/step_definitions/mount_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | When /^I assign the file '([^\']*)' to the '([^\']*)' column$/ do |path, column| 4 | @instance.send("#{column}=", File.open(file_path(path))) 5 | end 6 | 7 | Given /^the uploader class is mounted on the '([^\']*)' column$/ do |column| 8 | @mountee_klass.mount_uploader column.to_sym, @klass 9 | end 10 | 11 | When /^I retrieve the file later from the cache name for the column '([^\']*)'$/ do |column| 12 | new_instance = @instance.class.new 13 | new_instance.send("#{column}_cache=", @instance.send("#{column}_cache")) 14 | @instance = new_instance 15 | end 16 | 17 | Then /^the url for the column '([^\']*)' should be '([^\']*)'$/ do |column, url| 18 | @instance.send("#{column}_url").should == url 19 | end 20 | -------------------------------------------------------------------------------- /spec/generators/uploader_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'generator_spec' 3 | require 'generators/uploader_generator' 4 | 5 | describe UploaderGenerator, :type => :generator do 6 | destination File.expand_path("../../tmp", __FILE__) 7 | 8 | before :each do 9 | prepare_destination 10 | end 11 | 12 | it "should properly create uploader file" do 13 | run_generator %w(Avatar) 14 | assert_file 'app/uploaders/avatar_uploader.rb', /class AvatarUploader < CarrierWave::Uploader::Base/ 15 | end 16 | 17 | it "should properly create namespaced uploader file" do 18 | run_generator %w(MyModule::Avatar) 19 | assert_file 'app/uploaders/my_module/avatar_uploader.rb', /class MyModule::AvatarUploader < CarrierWave::Uploader::Base/ 20 | end 21 | end -------------------------------------------------------------------------------- /features/step_definitions/datamapper_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Given /^a datamapper class that has a '([^\']*)' column$/ do |column| 4 | @mountee_klass = Class.new do 5 | include DataMapper::Resource 6 | 7 | storage_names[:default] = 'users' 8 | 9 | property :id, DataMapper::Types::Serial 10 | property column.to_sym, String 11 | end 12 | @mountee_klass.auto_migrate! 13 | end 14 | 15 | Given /^an instance of the datamapper class$/ do 16 | @instance = @mountee_klass.new 17 | end 18 | 19 | When /^I save the datamapper record$/ do 20 | @instance.save 21 | end 22 | 23 | When /^I reload the datamapper record$/ do 24 | @instance = @instance.class.first(:id => @instance.key) 25 | end 26 | 27 | When /^I delete the datamapper record$/ do 28 | @instance.destroy 29 | end 30 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/serialization.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "json" 4 | require "active_support/core_ext/hash" 5 | 6 | module CarrierWave 7 | module Uploader 8 | module Serialization 9 | extend ActiveSupport::Concern 10 | 11 | def serializable_hash(options = nil) 12 | {"url" => url}.merge Hash[versions.map { |name, version| [name, { "url" => version.url }] }] 13 | end 14 | 15 | def as_json(options=nil) 16 | serializable_hash 17 | end 18 | 19 | def to_json(options=nil) 20 | JSON.generate(as_json) 21 | end 22 | 23 | def to_xml(options={}) 24 | merged_options = options.merge(:root => mounted_as || "uploader", :type => 'uploader') 25 | serializable_hash.to_xml(merged_options) 26 | end 27 | 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/activerecord.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE == 'jruby' 2 | require 'activerecord-jdbcpostgresql-adapter' 3 | else 4 | require 'pg' 5 | end 6 | require 'active_record' 7 | require 'carrierwave/orm/activerecord' 8 | 9 | # Change this if PG is unavailable 10 | dbconfig = { 11 | :adapter => 'postgresql', 12 | :database => 'carrierwave_test', 13 | :encoding => 'utf8', 14 | :username => 'postgres' 15 | } 16 | 17 | database = dbconfig.delete(:database) 18 | 19 | ActiveRecord::Base.establish_connection(dbconfig.merge(database: "template1")) 20 | begin 21 | ActiveRecord::Base.connection.create_database database 22 | rescue ActiveRecord::StatementInvalid => e # database already exists 23 | end 24 | ActiveRecord::Base.establish_connection(dbconfig.merge(:database => database)) 25 | 26 | ActiveRecord::Migration.verbose = false 27 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: 処理できませんでした 5 | carrierwave_integrity_error: は許可されていないファイルタイプです 6 | carrierwave_download_error: はダウンロードできません 7 | extension_white_list_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}" 8 | extension_black_list_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできないファイルタイプ: %{prohibited_types}" 9 | rmagick_processing_error: "rmagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}" 10 | mime_types_processing_error: "MIME::Typesのファイルを処理できませんでした。Content-Typeを確認してください。エラーメッセージ: %{e}" 11 | mini_magick_processing_error: "MiniMagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}" 12 | min_size_error: "ファイルを%{min_size}バイト以上のサイズにしてください" 13 | max_size_error: "ファイルを%{max_size}バイト以下のサイズにしてください" 14 | -------------------------------------------------------------------------------- /spec/uploader/mountable_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '#model' do 17 | it "should be remembered from initialization" do 18 | model = double('a model object') 19 | @uploader = @uploader_class.new(model) 20 | expect(@uploader.model).to eq(model) 21 | end 22 | end 23 | 24 | describe '#mounted_as' do 25 | it "should be remembered from initialization" do 26 | model = double('a model object') 27 | @uploader = @uploader_class.new(model, :llama) 28 | expect(@uploader.model).to eq(model) 29 | expect(@uploader.mounted_as).to eq(:llama) 30 | end 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /features/download.feature: -------------------------------------------------------------------------------- 1 | Feature: downloading files 2 | In order to allow users to upload remote files 3 | As a developer using CarrierWave 4 | I want to download files to the filesystem via HTTP 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And an instance of that class 9 | 10 | Scenario: download a file 11 | When I download the file 'http://s3.amazonaws.com/Monkey/testfile.txt' 12 | Then there should be a file called 'testfile.txt' somewhere in a subdirectory of 'public/uploads/tmp' 13 | And the file called 'testfile.txt' in a subdirectory of 'public/uploads/tmp' should contain 'S3 Remote File' 14 | 15 | Scenario: downloading a file then storing 16 | When I download the file 'http://s3.amazonaws.com/Monkey/testfile.txt' 17 | And I store the file 18 | Then there should be a file at 'public/uploads/testfile.txt' 19 | And the file at 'public/uploads/testfile.txt' should contain 'S3 Remote File' 20 | 21 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/cs.yml: -------------------------------------------------------------------------------- 1 | cs: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: se nepodařilo zpracovat 5 | carrierwave_integrity_error: není povolený typ souboru 6 | carrierwave_download_error: nemůže být stažen 7 | extension_white_list_error: "Není možné nahrávat %{extension} soubory, povolené typy: %{allowed_types}" 8 | extension_black_list_error: "Není možné nahrávat %{extension} soubory, zakázané typy: %{prohibited_types}" 9 | rmagick_processing_error: "Nepodařilo se upravit pomocí rmagick, možná se nejedná o obrázek? Hlášená Chyba: %{e}" 10 | mime_types_processing_error: "Nepodařilo se upravit s MIME::Types, možná se nejedná o content-type? Hlášená Chyba: %{e}" 11 | mini_magick_processing_error: "Nepodařilo se upravit pomocí MiniMagick, možná se nejedná o obrázek? Hlášená Chyba: %{e}" 12 | min_size_error: "Velikost souboru by měla být větší než %{min_size}" 13 | max_size_error: "Velikost souboru by měla být nižší než %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/sk.yml: -------------------------------------------------------------------------------- 1 | sk: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: sa nepodarilo spracovať 5 | carrierwave_integrity_error: nie je povolený typ súboru 6 | carrierwave_download_error: nie je možné stiahnuť 7 | extension_white_list_error: "Nie je možné nahrávať %{extension} súbory, povolené typy: %{allowed_types}" 8 | extension_black_list_error: "Nie je možné nahrávať %{extension} súbory, zakázané typy: %{prohibited_types}" 9 | rmagick_processing_error: "Nepodarilo sa upraviť pomocou rmagick, možno nejde o obrázok? Hlásená chyba: %{e}" 10 | mime_types_processing_error: "Súbor sa nepodarilo spracovať pomocou MIME::Types, možno nejde o valídny content-type? Hlásená chyba: %{e}" 11 | mini_magick_processing_error: "Nepodarilo sa upraviť pomocou MiniMagick, možno nejde o obrázok? Hlásená chyba: %{e}" 12 | min_size_error: "Filstorleken ska vara större än %{min_size}" 13 | max_size_error: "Filstorleken bör vara mindre än %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/nb.yml: -------------------------------------------------------------------------------- 1 | nb: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: kunne ikke behandles 5 | carrierwave_integrity_error: er ikke en tillatt filtype 6 | carrierwave_download_error: kunne ikke lastes ned 7 | extension_white_list_error: "Du kan ikke laste opp %{extension}-filer, tillatte filtyper: %{allowed_types}" 8 | extension_black_list_error: "Du kan ikke laste opp %{extension}-filer, forbudte filtyper: %{prohibited_types}" 9 | rmagick_processing_error: "Kunne ikke manipulere med rmagick. Er du sikker på at det er et bilde? Feilmelding: %{e}" 10 | mime_types_processing_error: "Kunne ikke behandle fil med MIME::Types. Er du sikker på at content-type er korrekt? Feilmelding: %{e}" 11 | mini_magick_processing_error: "Kunne ikke manipulere med MiniMagick. Er du sikker på at det er et bilde? Feilmelding: %{e}" 12 | min_size_error: "Filen størrelse bør være større enn %{min_size}" 13 | max_size_error: "Filen størrelse bør være mindre enn %{max_size}" 14 | -------------------------------------------------------------------------------- /spec/uploader/callback_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | it "should keep callbacks on different classes isolated" do 8 | @uploader_class_1 = Class.new(CarrierWave::Uploader::Base) 9 | 10 | # First Uploader only has default before-callback 11 | expect(@uploader_class_1._before_callbacks[:cache]).to eq([:check_whitelist!, :check_blacklist!, :check_size!, :process!]) 12 | 13 | @uploader_class_2 = Class.new(CarrierWave::Uploader::Base) 14 | @uploader_class_2.before :cache, :before_cache_callback 15 | 16 | # Second Uploader defined with another callback 17 | expect(@uploader_class_2._before_callbacks[:cache]).to eq([:check_whitelist!, :check_blacklist!, :check_size!, :process!, :before_cache_callback]) 18 | 19 | # Make sure the first Uploader doesn't inherit the same callback 20 | expect(@uploader_class_1._before_callbacks[:cache]).to eq([:check_whitelist!, :check_blacklist!, :check_size!, :process!]) 21 | end 22 | 23 | 24 | end 25 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/tr.yml: -------------------------------------------------------------------------------- 1 | tr: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: işlenmesi sırasında hata oluştu 5 | carrierwave_integrity_error: izin verilebilir bir dosya türü değil 6 | carrierwave_download_error: indirilemedi 7 | extension_white_list_error: "%{extension} uzantılı dosyaları yükleme izniniz yok, izin verilen uzantılar: %{allowed_types}" 8 | extension_black_list_error: "%{extension} uzantılı dosyaları yükleme izniniz yok, izin verilmeyen uzantılar: %{prohibited_types}" 9 | rmagick_processing_error: "Resim rmagick ile düzenlenemedi, belkide resim değildir? Orjinal Hata: %{e}" 10 | mime_types_processing_error: "Dosya, MIME::Types kullanılarak işlenemedi, belkide geçerli bir içerik türü değildir? Orjinal Hata: %{e}" 11 | mini_magick_processing_error: "Resim MiniMagick ile düzenlenemedi, belkide resim değildir? Orjinal Hata: %{e}" 12 | min_size_error: "Dosya boyutu daha büyük olmalıdır %{min_size}" 13 | max_size_error: "Dosya boyutu daha az olmalıdır %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/ru.yml: -------------------------------------------------------------------------------- 1 | ru: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: Невозможно обработать изображение 5 | carrierwave_integrity_error: Файл не является изображением 6 | carrierwave_download_error: Невозможно скачать файл 7 | extension_white_list_error: "Вы не можете загружать файлы типа %{extension}, разрешенные типы: %{allowed_types}" 8 | extension_black_list_error: "Вы не можете загружать файлы типа %{extension}, запрещенные типы: %{prohibited_types}" 9 | rmagick_processing_error: "Ошибка взаимодействия с RMagick, может быть это не изображение? Исходная ошибка: %{e}" 10 | mime_types_processing_error: "Не получилось обработать файл с MIME::Types, возможно неправильный content-type? Исходная ошибка: %{e}" 11 | mini_magick_processing_error: "Ошибка взаимодействия с MiniMagick, может быть это не изображение? Исходная ошибка: %{e}" 12 | min_size_error: "Размер файла должен быть больше, чем %{min_size}" 13 | max_size_error: "Размер файла должен быть меньше, чем %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: falhou em ser processado 5 | carrierwave_integrity_error: não é um tipo de arquivo permitido 6 | carrierwave_download_error: não pôde ser baixado 7 | extension_white_list_error: "Não é permitido o envio de arquivos %{extension}, tipos permitidos: %{allowed_types}" 8 | extension_black_list_error: "Não é permitido o envio de arquivos %{extension}, tipos proibidos: %{prohibited_types}" 9 | rmagick_processing_error: "Falha ao manipular com RMagick, talvez arquivo não seja uma imagem? Erro original: %{e}" 10 | mime_types_processing_error: "Falha ao processar arquivo com MIME::Types, talvez content-type seja inválido? Erro original: %{e}" 11 | mini_magick_processing_error: "Falha ao manipular com MiniMagick, talvez arquivo não seja uma imagem? Erro original: %{e}" 12 | min_size_error: "O tamanho do arquivo deve ser maior do que %{min_size}" 13 | max_size_error: "O tamanho do arquivo deve ser inferior a %{max_size}" 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.0 5 | - 2.1 6 | - 2.2 7 | - ruby-head 8 | - jruby 9 | - jruby-head 10 | 11 | gemfile: 12 | - Gemfile 13 | - gemfiles/rails-4-0-stable.gemfile 14 | - gemfiles/rails-4-1-stable.gemfile 15 | - gemfiles/rails-4-2-stable.gemfile 16 | - gemfiles/rails-master.gemfile 17 | 18 | sudo: false 19 | 20 | before_script: 21 | - psql -c 'create database carrierwave_test;' -U postgres 22 | 23 | matrix: 24 | exclude: 25 | - rvm: 2.0 26 | gemfile: gemfiles/rails-master.gemfile 27 | - rvm: jruby 28 | gemfile: gemfiles/rails-master.gemfile 29 | - rvm: jruby-head 30 | gemfile: gemfiles/rails-master.gemfile 31 | allow_failures: 32 | - rvm: ruby-head 33 | - rvm: jruby-head 34 | 35 | notifications: 36 | email: false 37 | slack: 38 | secure: Npzanyv/LXLIRlrNs8iTUbZNRhXlP+K2ZpjZoS2UKkr09jYyP1qdf5a//R3Lu7Yat7g2b4qTJGbaZBEMUQSVaJ6UX6quiBJjVWxjxjQ4Ugk8k/yOIAcGEGYPfS6YzRXemRwo9j4uy76cmwlv8cwEuYTSTBRK4XrdYHslX6pKSXM= 39 | 40 | addons: 41 | postgresql: "9.3" 42 | apt_packages: 43 | - libmagic-dev 44 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: konnte nicht verarbeitet werden 5 | carrierwave_integrity_error: ist kein erlaubter Dateityp 6 | carrierwave_download_error: konnte nicht heruntergeladen werden 7 | extension_white_list_error: "Sie sind nicht berechtigt %{extension} Dateien hochzuladen, erlaubte Typen: %{allowed_types}" 8 | extension_black_list_error: "Sie sind nicht berechtigt %{extension} Dateien hochzuladen, verbotene Typen: %{prohibited_types}" 9 | rmagick_processing_error: "Verarbeitung mit rmagick fehlgeschlagen, vielleicht ist es kein Bild? Original Fehler: %{e}" 10 | mime_types_processing_error: "Verarbeitung mit MIME::Types fehlgeschlagen, vielleicht kein gültiger content-type? Original Fehler: %{e}" 11 | mini_magick_processing_error: "Verarbeitung mit MiniMagick fehlgeschlagen, vielleicht ist es kein Bild? Original Fehler: %{e}" 12 | min_size_error: "Die dateigröße sollte größer sein als %{min_size}" 13 | max_size_error: "Die dateigröße sollte weniger als sein %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/es.yml: -------------------------------------------------------------------------------- 1 | es: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: no pudo ser procesado 5 | carrierwave_integrity_error: no es de un tipo de archivo permitido 6 | carrierwave_download_error: no pudo ser descargado 7 | extension_white_list_error: "No se pueden subir archivos de la extensión %{extension}. Las extensiones permitidas son: %{allowed_types}" 8 | extension_black_list_error: "No se pueden subir archivos de la extensión %{extension}. Las extensiones prohibidas son: %{prohibited_types}" 9 | rmagick_processing_error: "No se pudo manipular con rmagick, quizá porque no es una imágen? Error original: %{e}" 10 | mime_types_processing_error: "No se pudo procesar archivo con MIME::Types, quizá no tiene el content-type correcto? Error original: %{e}" 11 | mini_magick_processing_error: "No se pudo manipular con MiniMagick, quizá porque no es una imágen? Error original: %{e}" 12 | min_size_error: "El tamaño del archivo debe ser mayor que %{min_size}" 13 | max_size_error: "El tamaño del archivo debe ser inferior a %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/pl.yml: -------------------------------------------------------------------------------- 1 | pl: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: nie można przetworzyć 5 | carrierwave_integrity_error: niedozwolony typ pliku 6 | carrierwave_download_error: nie można pobrać pliku 7 | extension_white_list_error: "Nie można wgrać pliku o rozszerzeniu %{extension}, dozwolone typy plików: %{allowed_types}" 8 | extension_black_list_error: "Nie można wgrać pliku o rozszerzeniu %{extension}, zakazane typy plików: %{prohibited_types}" 9 | rmagick_processing_error: "Nie udało się przetworzyć pliku przy pomocy rmagick, może to nie jest obrazek? Oryginalna treść błędu: %{e}" 10 | mime_types_processing_error: "Nie udało się przetworzyć pliku przy pomocy MIME::Types, może content-type jest niepoprawny? Oryginalna treść błędu: %{e}" 11 | mini_magick_processing_error: "Nie udało się przetworzyć pliku przy pomocy MiniMagick, może to nie jest obrazek? Oryginalna treść błędu: %{e}" 12 | min_size_error: "Rozmiar pliku powinna być większa niż %{min_size}" 13 | max_size_error: "Rozmiar pliku powinna być mniejsza niż %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/nl.yml: -------------------------------------------------------------------------------- 1 | nl: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: kon niet worden verwerkt 5 | carrierwave_integrity_error: is niet van een toegestaan bestandstype 6 | carrierwave_download_error: kon niet gedownload worden 7 | extension_white_list_error: "Het is niet toegestaan om %{extension} bestanden te uploaden; toegestane bestandstypes: %{allowed_types}" 8 | extension_black_list_error: "Het is niet toegestaan om %{extension} bestanden te uploaden; verboden bestandstypes: %{prohibited_types}" 9 | rmagick_processing_error: "Bewerking met rmagick is mislukt, misschien is het geen afbeelding? Originele foutmelding: %{e}" 10 | mime_types_processing_error: "Verwerking van bestand met MIME::Types is mislukt, misschien is het geen geldig content-type? Originele foutmelding: %{e}" 11 | mini_magick_processing_error: "Bewerking met MiniMagick is mislukt, misschien is het geen afbeelding? Originele foutmelding: %{e}" 12 | min_size_error: "De bestandsgrootte moet groter zijn dan %{min_size}" 13 | max_size_error: "De bestandsgrootte moet minder zijn dan %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/callbacks.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Callbacks 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | class_attribute :_before_callbacks, :_after_callbacks, 10 | :instance_writer => false 11 | self._before_callbacks = Hash.new [] 12 | self._after_callbacks = Hash.new [] 13 | end 14 | 15 | def with_callbacks(kind, *args) 16 | self.class._before_callbacks[kind].each { |c| send c, *args } 17 | yield 18 | self.class._after_callbacks[kind].each { |c| send c, *args } 19 | end 20 | 21 | module ClassMethods 22 | def before(kind, callback) 23 | self._before_callbacks = self._before_callbacks. 24 | merge kind => _before_callbacks[kind] + [callback] 25 | end 26 | 27 | def after(kind, callback) 28 | self._after_callbacks = self._after_callbacks. 29 | merge kind => _after_callbacks[kind] + [callback] 30 | end 31 | end # ClassMethods 32 | 33 | end # Callbacks 34 | end # Uploader 35 | end # CarrierWave 36 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/fr-CA.yml: -------------------------------------------------------------------------------- 1 | fr_CA: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: "Impossible de redimensionner l'image." 5 | carrierwave_integrity_error: "Ce n'est pas une image." 6 | carrierwave_download_error: "Impossible de télécharger l'image." 7 | extension_white_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types autorisés: %{allowed_types}" 8 | extension_black_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types interdits: %{prohibited_types}" 9 | rmagick_processing_error: "La manipulation d'image avec rmagick a échoué. Peut-être que ce n'est pas une image? Erreur originale: %{e}" 10 | mime_types_processing_error: "Le traitement de fichier avec MIME::Types a échoué. Peut-être que ce n'est pas un type valide? Erreur originale: %{e}" 11 | mini_magick_processing_error: "La manipulation d'image avec MiniMagick a échoué. Peut-être que ce n'est pas une image? Erreur originale: %{e}" 12 | min_size_error: "La taille du fichier doit être supérieure à %{min_size}" 13 | max_size_error: "La taille du fichier doit être inférieure à %{max_size}" 14 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/pt-PT.yml: -------------------------------------------------------------------------------- 1 | pt-PT: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: falhou ao ser processado 5 | carrierwave_integrity_error: não é um tipo de ficheiro permitido 6 | carrierwave_download_error: não pôde ser transferido 7 | extension_white_list_error: "Não é permitido o envio de ficheiros com a extensão %{extension}, tipos de ficheiro permitidos: %{allowed_types}" 8 | extension_black_list_error: "Não é permitido o envio de ficheiros com a extensão %{extension}, tipos de ficheiro proibidos: %{prohibited_types}" 9 | rmagick_processing_error: "Ocorreu uma falha ao processar com rmagick, talvez o ficheiro não seja uma imagem? Erro original: %{e}" 10 | mime_types_processing_error: "Ocorreu uma falha ao processar com MIME::Types, talvez o parâmetro content-type não seja válido? Erro original: %{e}" 11 | mini_magick_processing_error: "Ocorreu uma falha ao processar com MiniMagick, talvez o ficheiro não seja uma imagem? Erro original: %{e}" 12 | min_size_error: "O tamanho do arquivo deve ser maior do que %{min_size}" 13 | max_size_error: "O tamanho do arquivo deve ser inferior a %{max_size}" 14 | -------------------------------------------------------------------------------- /spec/storage/fog_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'fog' 5 | require 'carrierwave/storage/fog' 6 | 7 | unless ENV['REMOTE'] == 'true' 8 | Fog.mock! 9 | end 10 | 11 | require_relative './fog_credentials' # after Fog.mock! 12 | require_relative './fog_helper' 13 | 14 | FOG_CREDENTIALS.each do |credential| 15 | fog_tests(credential) 16 | end 17 | 18 | describe CarrierWave::Storage::Fog::File do 19 | describe "#filename" do 20 | subject{ CarrierWave::Storage::Fog::File.new(nil, nil, nil) } 21 | 22 | context "with normal url" do 23 | before do 24 | allow(subject).to receive(:url){ 'http://example.com/path/to/foo.txt' } 25 | end 26 | 27 | it "should extract filename from url" do 28 | expect(subject.filename).to eq('foo.txt') 29 | end 30 | end 31 | 32 | context "when url contains '/' in query string" do 33 | before do 34 | allow(subject).to receive(:url){ 'http://example.com/path/to/foo.txt?bar=baz/fubar' } 35 | end 36 | 37 | it "should extract correct part" do 38 | expect(subject.filename).to eq('foo.txt') 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: failed to be processed 5 | carrierwave_integrity_error: is not of an allowed file type 6 | carrierwave_download_error: could not be downloaded 7 | extension_white_list_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" 8 | extension_black_list_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}" 9 | mime_type_pattern_white_list_error: "You are not allowed to upload %{content_type} files" 10 | mime_type_pattern_black_list_error: "You are not allowed to upload %{content_type} files" 11 | rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?" 12 | mime_types_processing_error: "Failed to process file with MIME::Types, maybe not valid content-type? Original Error: %{e}" 13 | mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}" 14 | min_size_error: "File size should be greater than %{min_size}" 15 | max_size_error: "File size should be less than %{max_size}" 16 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/url.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Url 6 | extend ActiveSupport::Concern 7 | include CarrierWave::Uploader::Configuration 8 | include CarrierWave::Utilities::Uri 9 | 10 | ## 11 | # === Parameters 12 | # 13 | # [Hash] optional, the query params (only AWS) 14 | # 15 | # === Returns 16 | # 17 | # [String] the location where this file is accessible via a url 18 | # 19 | def url(options = {}) 20 | if file.respond_to?(:url) and not file.url.blank? 21 | file.method(:url).arity == 0 ? file.url : file.url(options) 22 | elsif file.respond_to?(:path) 23 | path = encode_path(file.path.sub(File.expand_path(root), '')) 24 | 25 | if host = asset_host 26 | if host.respond_to? :call 27 | "#{host.call(file)}#{path}" 28 | else 29 | "#{host}#{path}" 30 | end 31 | else 32 | (base_path || "") + path 33 | end 34 | end 35 | end 36 | 37 | def to_s 38 | url || '' 39 | end 40 | 41 | end # Url 42 | end # Uploader 43 | end # CarrierWave 44 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/el.yml: -------------------------------------------------------------------------------- 1 | el: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: "απέτυχε στην επεξεργασία" 5 | carrierwave_integrity_error: "δεν ανήκει σε επιτρεπτό τύπο αρχείου" 6 | carrierwave_download_error: "δεν ήταν δυνατό να μεταφορτωθεί" 7 | extension_white_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων %{extension}, επιτρεπτοί τύποι: %{allowed_types}" 8 | extension_black_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων %{extension}, μη επιτρεπτοί τύποι: %{prohibited_types}" 9 | mime_type_pattern_white_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων τύπου %{content_type}" 10 | mime_type_pattern_black_list_error: "Δεν επιτρέπεται το ανέβασμα αρχείων τύπου %{content_type}" 11 | rmagick_processing_error: "Απέτυχε ο χειρισμός με rmagick, ίσως δεν είναι εικόνα; Αρχικό Σφάλμα: %{e}" 12 | mime_types_processing_error: "Απέτυχε η επεξεργασία του αρχείου με MIME::Types, ίσως δεν έχει έγκυρο content-type; Αρχικό Σφάλμα: %{e}" 13 | mini_magick_processing_error: "Απέτυχε ο χειρισμός με MiniMagick, ίσως δεν είναι εικόνα; Αρχικό Σφάλμα: %{e}" 14 | min_size_error: "το μέγεθος του αρχείου θα πρέπει να είναι μεγαλύτερο από %{min_size}" 15 | max_size_error: "το μέγεθος του αρχείου θα πρέπει να είναι μικρότερο από %{max_size}" 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CarrierWave 2 | 3 | CarrierWave thrives on a large number of [contributors](https://github.com/carrierwaveuploader/carrierwave/contributors), 4 | and pull requests are very welcome. Before submitting a pull request, please make sure that your changes are well tested. 5 | 6 | First, make sure you have `imagemagick` and `ghostscript` installed. You may need `libmagic` as well. 7 | 8 | Then, you'll need to install bundler and the gem dependencies: 9 | 10 | `gem install bundler && bundle install` 11 | 12 | You should now be able to run the local tests: 13 | 14 | `bundle exec rake` 15 | 16 | You can also run the remote specs by creating a ~/.fog file: 17 | 18 | ```yaml 19 | :carrierwave: 20 | :aws_access_key_id: xxx 21 | :aws_secret_access_key: yyy 22 | :rackspace_username: xxx 23 | :rackspace_api_key: yyy 24 | :google_storage_access_key_id: xxx 25 | :google_storage_secret_access_key: yyy 26 | ``` 27 | 28 | You should now be able to run the remote tests: 29 | 30 | REMOTE=true bundle exec rake 31 | 32 | Please test with the latest Ruby 1.8.x and 1.9.x versions using RVM if possible. 33 | 34 | ## Running active record tests 35 | 36 | Make sure you have a local PostgreSQL database named `carrierwave_test` with the username 37 | `postgres` 38 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/file_size.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module FileSize 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | before :cache, :check_size! 10 | end 11 | 12 | ## 13 | # Override this method in your uploader to provide a Range of Size which 14 | # are allowed to be uploaded. 15 | # === Returns 16 | # 17 | # [NilClass, Range] a size range which are permitted to be uploaded 18 | # 19 | # === Examples 20 | # 21 | # def size_range 22 | # 3256...5748 23 | # end 24 | # 25 | def size_range; end 26 | 27 | private 28 | 29 | def check_size!(new_file) 30 | size = new_file.size 31 | expected_size_range = size_range 32 | if expected_size_range.is_a?(::Range) 33 | if size < expected_size_range.min 34 | raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.min_size_error", :min_size => expected_size_range.min) 35 | elsif size > expected_size_range.max 36 | raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.max_size_error", :max_size => expected_size_range.max) 37 | end 38 | end 39 | end 40 | 41 | end # FileSize 42 | end # Uploader 43 | end # CarrierWave 44 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/mountable.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Mountable 6 | 7 | attr_reader :model, :mounted_as 8 | 9 | ## 10 | # If a model is given as the first parameter, it will be stored in the 11 | # uploader, and available through +#model+. Likewise, mounted_as stores 12 | # the name of the column where this instance of the uploader is mounted. 13 | # These values can then be used inside your uploader. 14 | # 15 | # If you do not wish to mount your uploaders with the ORM extensions in 16 | # -more then you can override this method inside your uploader. Just be 17 | # sure to call +super+ 18 | # 19 | # === Parameters 20 | # 21 | # [model (Object)] Any kind of model object 22 | # [mounted_as (Symbol)] The name of the column where this uploader is mounted 23 | # 24 | # === Examples 25 | # 26 | # class MyUploader < CarrierWave::Uploader::Base 27 | # 28 | # def store_dir 29 | # File.join('public', 'files', mounted_as, model.permalink) 30 | # end 31 | # end 32 | # 33 | def initialize(model=nil, mounted_as=nil) 34 | @model = model 35 | @mounted_as = mounted_as 36 | end 37 | 38 | end # Mountable 39 | end # Uploader 40 | end # CarrierWave 41 | -------------------------------------------------------------------------------- /lib/carrierwave/locale/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | errors: 3 | messages: 4 | carrierwave_processing_error: "Impossible de redimensionner l'image." 5 | carrierwave_integrity_error: "Ce n'est pas une image." 6 | carrierwave_download_error: "Impossible de télécharger l'image." 7 | extension_white_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types autorisés: %{allowed_types}" 8 | extension_black_list_error: "Vous n'êtes pas autorisé à uploader des fichiers %{extension}, types interdits: %{prohibited_types}" 9 | mime_type_pattern_white_list_error: "Vous n'êtes pas autorisé à uploader des fichiers de type %{content_type}" 10 | mime_type_pattern_black_list_error: "Vous n'êtes pas autorisé à uploader des fichiers de type %{content_type}" 11 | rmagick_processing_error: "La manipulation d'image avec rmagick a échoué. Peut-être que ce n'est pas une image ? Erreur originale: %{e}" 12 | mime_types_processing_error: "Le traitement de fichier avec MIME::Types a échoué. Peut-être que ce n'est pas un type valide ? Erreur originale: %{e}" 13 | mini_magick_processing_error: "La manipulation d'image avec MiniMagick a échoué. Peut-être que ce n'est pas une image ? Erreur originale: %{e}" 14 | min_size_error: "La taille du fichier doit être supérieure à %{min_size}" 15 | max_size_error: "La taille du fichier doit être inférieure à %{max_size}" 16 | -------------------------------------------------------------------------------- /lib/carrierwave/storage/abstract.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Storage 5 | 6 | ## 7 | # This file serves mostly as a specification for Storage engines. There is no requirement 8 | # that storage engines must be a subclass of this class. 9 | # 10 | class Abstract 11 | 12 | attr_reader :uploader 13 | 14 | def initialize(uploader) 15 | @uploader = uploader 16 | end 17 | 18 | def identifier 19 | uploader.filename 20 | end 21 | 22 | def store!(file) 23 | end 24 | 25 | def retrieve!(identifier) 26 | end 27 | 28 | def cache!(new_file) 29 | raise NotImplementedError.new("Need to implement #cache! if you want to use #{self.class.name} as a cache storage.") 30 | end 31 | 32 | def retrieve_from_cache!(identifier) 33 | raise NotImplementedError.new("Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage.") 34 | end 35 | 36 | def delete_dir!(path) 37 | raise NotImplementedError.new("Need to implement #delete_dir! if you want to use #{self.class.name} as a cache storage.") 38 | end 39 | 40 | def clean_cache!(seconds) 41 | raise NotImplementedError.new("Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage.") 42 | end 43 | end # Abstract 44 | end # Storage 45 | end # CarrierWave 46 | -------------------------------------------------------------------------------- /spec/storage/fog_credentials.rb: -------------------------------------------------------------------------------- 1 | unless defined?(FOG_CREDENTIALS) 2 | 3 | credentials = [] 4 | 5 | if Fog.mocking? 6 | # Local and Rackspace don't have fog double support yet 7 | mappings = { 8 | 'AWS' => [:aws_access_key_id, :aws_secret_access_key], 9 | 'Google' => [:google_storage_access_key_id, :google_storage_secret_access_key], 10 | # 'Local' => [:local_root], 11 | # 'Rackspace' => [:rackspace_api_key, :rackspace_username] 12 | } 13 | 14 | mappings.each do |provider, keys| 15 | data = {:provider => provider} 16 | keys.each do |key| 17 | data[key] = key.to_s 18 | end 19 | credentials << data 20 | end 21 | 22 | FOG_CREDENTIALS = credentials 23 | else 24 | Fog.credential = :carrierwave 25 | 26 | mappings = { 27 | 'AWS' => [:aws_access_key_id, :aws_secret_access_key], 28 | 'Google' => [:google_storage_access_key_id, :google_storage_secret_access_key], 29 | 'Local' => [:local_root], 30 | 'Rackspace' => [:rackspace_api_key, :rackspace_username] 31 | } 32 | 33 | mappings.each do |provider, keys| 34 | unless (creds = Fog.credentials.reject {|key, value| ![*keys].include?(key)}).empty? 35 | data = {:provider => provider} 36 | keys.each do |key| 37 | data[key] = creds[key] 38 | end 39 | credentials << data 40 | end 41 | end 42 | 43 | FOG_CREDENTIALS = credentials 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/extension_blacklist.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | module Uploader 3 | module ExtensionBlacklist 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | before :cache, :check_blacklist! 8 | end 9 | 10 | ## 11 | # Override this method in your uploader to provide a black list of extensions which 12 | # are prohibited to be uploaded. Compares the file's extension case insensitive. 13 | # Furthermore, not only strings but Regexp are allowed as well. 14 | # 15 | # When using a Regexp in the black list, `\A` and `\z` are automatically added to 16 | # the Regexp expression, also case insensitive. 17 | # 18 | # === Returns 19 | 20 | # [NilClass, Array[String,Regexp]] a black list of extensions which are prohibited to be uploaded 21 | # 22 | # === Examples 23 | # 24 | # def extension_black_list 25 | # %w(swf tiff) 26 | # end 27 | # 28 | # Basically the same, but using a Regexp: 29 | # 30 | # def extension_black_list 31 | # [/swf/, 'tiff'] 32 | # end 33 | # 34 | 35 | def extension_black_list; end 36 | 37 | private 38 | 39 | def check_blacklist!(new_file) 40 | extension = new_file.extension.to_s 41 | if extension_black_list and extension_black_list.detect { |item| extension =~ /\A#{item}\z/i } 42 | raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_black_list_error", :extension => new_file.extension.inspect, :prohibited_types => extension_black_list.join(", ")) 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/uploader/magic_mime_blacklist_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader, filemagic: true do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) do 9 | include CarrierWave::Uploader::MagicMimeBlacklist 10 | 11 | # Accepts only images 12 | def blacklist_mime_type_pattern 13 | /image\// 14 | end 15 | end 16 | 17 | @uploader = @uploader_class.new 18 | end 19 | 20 | after do 21 | FileUtils.rm_rf(public_path) 22 | end 23 | 24 | describe '#cache!' do 25 | 26 | before do 27 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 28 | end 29 | 30 | context "when the is no blacklist pattern" do 31 | before do 32 | allow(@uploader).to receive(:blacklist_mime_type_pattern).and_return(nil) 33 | end 34 | 35 | it "does not raise an integrity error" do 36 | expect { 37 | @uploader.cache!(File.open(file_path('ruby.gif'))) 38 | }.not_to raise_error 39 | end 40 | end 41 | 42 | context "when there is a blacklist pattern" do 43 | context "and the file has compliant content-type" do 44 | it "does not raise an integrity error" do 45 | expect { 46 | @uploader.cache!(File.open(file_path('bork.txt'))) 47 | }.not_to raise_error 48 | end 49 | end 50 | 51 | context "and the file has not compliant content-type" do 52 | it "raises an integrity error" do 53 | expect { 54 | @uploader.cache!(File.open(file_path('ruby.gif'))) 55 | }.to raise_error 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/uploader/magic_mime_whitelist_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader, filemagic: true do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) do 9 | include CarrierWave::Uploader::MagicMimeWhitelist 10 | 11 | # Accepts only images 12 | def whitelist_mime_type_pattern 13 | /image\// 14 | end 15 | end 16 | 17 | @uploader = @uploader_class.new 18 | end 19 | 20 | after do 21 | FileUtils.rm_rf(public_path) 22 | end 23 | 24 | describe '#cache!' do 25 | 26 | before do 27 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 28 | end 29 | 30 | context "when the is no whitelist pattern" do 31 | before do 32 | allow(@uploader).to receive(:whitelist_mime_type_pattern).and_return(nil) 33 | end 34 | 35 | it "does not raise an integrity error" do 36 | expect { 37 | @uploader.cache!(File.open(file_path('ruby.gif'))) 38 | }.not_to raise_error 39 | end 40 | end 41 | 42 | context "when there is a whitelist pattern" do 43 | context "and the file has compliant content-type" do 44 | it "does not raise an integrity error" do 45 | expect { 46 | @uploader.cache!(File.open(file_path('ruby.gif'))) 47 | }.not_to raise_error 48 | end 49 | end 50 | 51 | context "and the file has not compliant content-type" do 52 | it "raises an integrity error" do 53 | expect { 54 | @uploader.cache!(File.open(file_path('bork.txt'))) 55 | }.to raise_error 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/generators/templates/uploader.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | class <%= class_name %>Uploader < CarrierWave::Uploader::Base 4 | 5 | # Include RMagick or MiniMagick support: 6 | # include CarrierWave::RMagick 7 | # include CarrierWave::MiniMagick 8 | 9 | # Choose what kind of storage to use for this uploader: 10 | storage :file 11 | # storage :fog 12 | 13 | # Override the directory where uploaded files will be stored. 14 | # This is a sensible default for uploaders that are meant to be mounted: 15 | def store_dir 16 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 17 | end 18 | 19 | # Provide a default URL as a default if there hasn't been a file uploaded: 20 | # def default_url 21 | # # For Rails 3.1+ asset pipeline compatibility: 22 | # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) 23 | # 24 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_') 25 | # end 26 | 27 | # Process files as they are uploaded: 28 | # process :scale => [200, 300] 29 | # 30 | # def scale(width, height) 31 | # # do something 32 | # end 33 | 34 | # Create different versions of your uploaded files: 35 | # version :thumb do 36 | # process :resize_to_fit => [50, 50] 37 | # end 38 | 39 | # Add a white list of extensions which are allowed to be uploaded. 40 | # For images you might use something like this: 41 | # def extension_white_list 42 | # %w(jpg jpeg gif png) 43 | # end 44 | 45 | # Override the filename of the uploaded files: 46 | # Avoid using model.id or version_name here, see uploader/store.rb for details. 47 | # def filename 48 | # "something.jpg" if original_filename 49 | # end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /spec/processing/magic_mime_types_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::MagicMimeTypes, :filemagic => true do 6 | 7 | before do 8 | @klass = Class.new(CarrierWave::Uploader::Base) do 9 | attr_accessor :content_type 10 | include CarrierWave::MagicMimeTypes 11 | end 12 | @instance = @klass.new 13 | FileUtils.cp(file_path('ruby.gif'), file_path('ruby_copy.gif')) 14 | allow(@instance).to receive(:original_filename).and_return file_path('ruby_copy.gif') 15 | allow(@instance).to receive(:file).and_return CarrierWave::SanitizedFile.new(file_path('ruby_copy.gif')) 16 | @file = @instance.file 17 | end 18 | 19 | after do 20 | FileUtils.rm(file_path('ruby_copy.gif')) 21 | end 22 | 23 | describe "#set_content_type" do 24 | it "does not set the content_type if already set" do 25 | @instance.file.content_type = 'image/jpeg' 26 | expect(@instance.file).not_to receive(:content_type=) 27 | @instance.set_content_type 28 | end 29 | 30 | it "sets content_type if content_type is nil" do 31 | pending 32 | @instance.file.content_type = nil 33 | expect(@instance.file).to receive(:content_type=).with('image/png') 34 | @instance.set_content_type 35 | end 36 | 37 | it "sets content_type if content_type is blank" do 38 | @instance.file.content_type = '' 39 | expect(@instance.file).to receive(:content_type=).with('image/png') 40 | @instance.set_content_type 41 | end 42 | 43 | it "sets content_type if override is true" do 44 | @instance.file.content_type = 'image/png' 45 | expect(@instance.file).to receive(:content_type=).with('image/png') 46 | @instance.set_content_type(true) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /features/caching.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage 2 | In order to be able to temporarily store files to disk 3 | As a developer using CarrierWave 4 | I want to cache files 5 | 6 | Scenario: cache a file 7 | Given an uploader class that uses the 'file' storage 8 | And an instance of that class 9 | When I cache the file 'fixtures/bork.txt' 10 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 11 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 12 | 13 | Scenario: cache two files in succession 14 | Given an uploader class that uses the 'file' storage 15 | And an instance of that class 16 | When I cache the file 'fixtures/bork.txt' 17 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 18 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 19 | When I cache the file 'fixtures/monkey.txt' 20 | Then there should be a file called 'monkey.txt' somewhere in a subdirectory of 'public/uploads/tmp' 21 | And the file called 'monkey.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/monkey.txt' 22 | 23 | Scenario: retrieving a file from cache 24 | Given an uploader class that uses the 'file' storage 25 | And an instance of that class 26 | And the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 27 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 28 | Then the uploader should have 'public/uploads/tmp/1369894322-345-2255/bork.txt' as its current path 29 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/extension_whitelist.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module ExtensionWhitelist 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | before :cache, :check_whitelist! 10 | end 11 | 12 | ## 13 | # Override this method in your uploader to provide a white list of extensions which 14 | # are allowed to be uploaded. Compares the file's extension case insensitive. 15 | # Furthermore, not only strings but Regexp are allowed as well. 16 | # 17 | # When using a Regexp in the white list, `\A` and `\z` are automatically added to 18 | # the Regexp expression, also case insensitive. 19 | # 20 | # === Returns 21 | # 22 | # [NilClass, Array[String,Regexp]] a white list of extensions which are allowed to be uploaded 23 | # 24 | # === Examples 25 | # 26 | # def extension_white_list 27 | # %w(jpg jpeg gif png) 28 | # end 29 | # 30 | # Basically the same, but using a Regexp: 31 | # 32 | # def extension_white_list 33 | # [/jpe?g/, 'gif', 'png'] 34 | # end 35 | # 36 | def extension_white_list; end 37 | 38 | private 39 | 40 | def check_whitelist!(new_file) 41 | extension = new_file.extension.to_s 42 | if extension_white_list and not extension_white_list.detect { |item| extension =~ /\A#{item}\z/i } 43 | raise CarrierWave::IntegrityError, I18n.translate(:"errors.messages.extension_white_list_error", :extension => new_file.extension.inspect, :allowed_types => extension_white_list.join(", ")) 44 | end 45 | end 46 | 47 | end # ExtensionWhitelist 48 | end # Uploader 49 | end # CarrierWave 50 | -------------------------------------------------------------------------------- /spec/uploader/file_size_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | before do 7 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 8 | @uploader = @uploader_class.new 9 | end 10 | 11 | after do 12 | FileUtils.rm_rf(public_path) 13 | end 14 | 15 | describe '#cache!' do 16 | 17 | before do 18 | allow(CarrierWave).to receive(:generate_cache_id).and_return('20071201-1234-2255') 19 | end 20 | 21 | it "should not raise an integrity error if there is no range specified" do 22 | allow(@uploader).to receive(:size_range).and_return(nil) 23 | expect(running { 24 | @uploader.cache!(File.open(file_path('test.jpg'))) 25 | }).not_to raise_error 26 | end 27 | 28 | it "should raise an integrity error if there is a size range and file has size less than minimum" do 29 | allow(@uploader).to receive(:size_range).and_return(2097152..4194304) 30 | expect(running { 31 | @uploader.cache!(File.open(file_path('test.jpg'))) 32 | }).to raise_error(CarrierWave::IntegrityError) 33 | end 34 | 35 | it "should raise an integrity error if there is a size range and file has size more than maximum" do 36 | allow(@uploader).to receive(:size_range).and_return(0..10) 37 | expect(running { 38 | @uploader.cache!(File.open(file_path('test.jpg'))) 39 | }).to raise_error(CarrierWave::IntegrityError) 40 | end 41 | 42 | it "should not raise an integrity error if there is a size range the file is not on it" do 43 | allow(@uploader).to receive(:size_range).and_return(0..50) 44 | expect(running { 45 | @uploader.cache!(File.open(file_path('test.jpg'))) 46 | }).not_to raise_error 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /carrierwave.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require 'carrierwave/version' 6 | require 'date' 7 | 8 | Gem::Specification.new do |s| 9 | s.name = "carrierwave" 10 | s.version = CarrierWave::VERSION 11 | 12 | s.authors = ["Jonas Nicklas"] 13 | s.date = Date.today 14 | s.description = "Upload files in your Ruby applications, map them to a range of ORMs, store them on different backends." 15 | s.summary = "Ruby file upload library" 16 | s.email = ["jonas.nicklas@gmail.com"] 17 | s.extra_rdoc_files = ["README.md"] 18 | s.files = Dir["{bin,lib}/**/*", "README.md"] 19 | s.homepage = %q{https://github.com/carrierwaveuploader/carrierwave} 20 | s.rdoc_options = ["--main"] 21 | s.require_paths = ["lib"] 22 | s.rubyforge_project = %q{carrierwave} 23 | s.rubygems_version = %q{1.3.5} 24 | s.specification_version = 3 25 | s.licenses = ["MIT"] 26 | 27 | s.add_dependency "activesupport", ">= 3.2.0" 28 | s.add_dependency "activemodel", ">= 3.2.0" 29 | s.add_dependency "json", ">= 1.7" 30 | s.add_dependency "mime-types", ">= 1.16" 31 | if RUBY_ENGINE == 'jruby' 32 | s.add_development_dependency 'activerecord-jdbcpostgresql-adapter' 33 | else 34 | s.add_development_dependency "pg" 35 | end 36 | s.add_development_dependency "rails", ">= 3.2.0" 37 | s.add_development_dependency "cucumber", "~> 2.0.0" 38 | s.add_development_dependency "rspec", "~> 3.2.0" 39 | s.add_development_dependency "sham_rack" 40 | s.add_development_dependency "fog", ">= 1.28.0" 41 | s.add_development_dependency "mini_magick", ">= 3.6.0" 42 | if RUBY_ENGINE != 'jruby' 43 | s.add_development_dependency "rmagick" 44 | s.add_development_dependency "ruby-filemagic", ">= 0.6.3" 45 | end 46 | s.add_development_dependency "nokogiri", "~> 1.6.3" 47 | s.add_development_dependency "timecop", "0.7.1" 48 | s.add_development_dependency "generator_spec" 49 | s.add_development_dependency "pry" 50 | end 51 | -------------------------------------------------------------------------------- /features/step_definitions/file_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ### 4 | # EXISTENCE 5 | 6 | Then /^there should be a file at '(.*?)'$/ do |file| 7 | File.exist?(file_path(file)).should be_truthy 8 | end 9 | 10 | Then /^there should not be a file at '(.*?)'$/ do |file| 11 | File.exist?(file_path(file)).should be_falsey 12 | end 13 | 14 | Then /^there should be a file called '(.*?)' somewhere in a subdirectory of '(.*?)'$/ do |file, directory| 15 | Dir.glob(File.join(file_path(directory), '**', file)).any?.should be_truthy 16 | end 17 | 18 | ### 19 | # IDENTICAL 20 | 21 | Then /^the file at '(.*?)' should be identical to the file at '(.*?)'$/ do |one, two| 22 | File.read(file_path(one)).should == File.read(file_path(two)) 23 | end 24 | 25 | Then /^the file at '(.*?)' should not be identical to the file at '(.*?)'$/ do |one, two| 26 | File.read(file_path(one)).should_not == File.read(file_path(two)) 27 | end 28 | 29 | Then /^the file called '(.*?)' in a subdirectory of '(.*?)' should be identical to the file at '(.*?)'$/ do |file, directory, other| 30 | File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should == File.read(file_path(other)) 31 | end 32 | 33 | Then /^the file called '(.*?)' in a subdirectory of '(.*?)' should not be identical to the file at '(.*?)'$/ do |file, directory, other| 34 | File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should_not == File.read(file_path(other)) 35 | end 36 | 37 | ### 38 | # CONTENT 39 | 40 | Then /^the file called '([^']+)' in a subdirectory of '([^']+)' should contain '([^']+)'$/ do |file, directory, content| 41 | File.read(Dir.glob(File.join(file_path(directory), '**', file)).first).should include(content) 42 | end 43 | 44 | Then /^the file at '([^']+)' should contain '([^']+)'$/ do |path, content| 45 | File.read(file_path(path)).should include(content) 46 | end 47 | 48 | ### 49 | # REVERSING 50 | 51 | Then /^the file at '(.*?)' should be the reverse of the file at '(.*?)'$/ do |one, two| 52 | File.read(file_path(one)).should == File.read(file_path(two)).reverse 53 | end 54 | -------------------------------------------------------------------------------- /features/file_storage.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And an instance of that class 9 | 10 | Scenario: store a file 11 | When I store the file 'fixtures/bork.txt' 12 | Then there should be a file at 'public/uploads/bork.txt' 13 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 14 | 15 | Scenario: store two files in succession 16 | When I store the file 'fixtures/bork.txt' 17 | Then there should be a file at 'public/uploads/bork.txt' 18 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 19 | When I store the file 'fixtures/monkey.txt' 20 | Then there should be a file at 'public/uploads/monkey.txt' 21 | And the file at 'public/uploads/monkey.txt' should be identical to the file at 'fixtures/monkey.txt' 22 | 23 | Scenario: cache a file and then store it 24 | When I cache the file 'fixtures/bork.txt' 25 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 26 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 27 | And there should not be a file at 'public/uploads/bork.txt' 28 | When I store the file 29 | Then there should be a file at 'public/uploads/bork.txt' 30 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 31 | 32 | Scenario: retrieving a file from cache then storing 33 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 34 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 35 | And I store the file 36 | Then there should be a file at 'public/uploads/bork.txt' 37 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 38 | -------------------------------------------------------------------------------- /spec/uploader/proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '#blank?' do 17 | it "should be true when nothing has been done" do 18 | expect(@uploader).to be_blank 19 | end 20 | 21 | it "should not be true when the file is empty" do 22 | @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') 23 | expect(@uploader).to be_blank 24 | end 25 | 26 | it "should not be true when a file has been cached" do 27 | @uploader.cache!(File.open(file_path('test.jpg'))) 28 | expect(@uploader).not_to be_blank 29 | end 30 | end 31 | 32 | describe '#read' do 33 | it "should be nil by default" do 34 | expect(@uploader.read).to be_nil 35 | end 36 | 37 | it "should read the contents of a cached file" do 38 | @uploader.cache!(File.open(file_path('test.jpg'))) 39 | expect(@uploader.read).to eq("this is stuff") 40 | end 41 | end 42 | 43 | describe '#size' do 44 | it "should be zero by default" do 45 | expect(@uploader.size).to eq(0) 46 | end 47 | 48 | it "should get the size of a cached file" do 49 | @uploader.cache!(File.open(file_path('test.jpg'))) 50 | expect(@uploader.size).to eq(13) 51 | end 52 | end 53 | 54 | describe '#content_type' do 55 | it "should be nil when nothing has been done" do 56 | expect(@uploader.content_type).to be_nil 57 | end 58 | 59 | it "should get the content type when the file has been cached" do 60 | @uploader.cache!(File.open(file_path('test.jpg'))) 61 | expect(@uploader.content_type).to eq('image/jpeg') 62 | end 63 | 64 | it "should get the content type when the file is empty" do 65 | @uploader.retrieve_from_cache!('1369894322-345-2255/test.jpeg') 66 | expect(@uploader.content_type).to eq('image/jpeg') 67 | end 68 | end 69 | 70 | end 71 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/proxy.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Proxy 6 | 7 | ## 8 | # === Returns 9 | # 10 | # [Boolean] Whether the uploaded file is blank 11 | # 12 | def blank? 13 | file.blank? 14 | end 15 | 16 | ## 17 | # === Returns 18 | # 19 | # [String] the path where the file is currently located. 20 | # 21 | def current_path 22 | file.path if file.respond_to?(:path) 23 | end 24 | 25 | alias_method :path, :current_path 26 | 27 | ## 28 | # Returns a string that uniquely identifies the last stored file 29 | # 30 | # === Returns 31 | # 32 | # [String] uniquely identifies a file 33 | # 34 | def identifier 35 | storage.identifier if storage.respond_to?(:identifier) 36 | end 37 | 38 | ## 39 | # Read the contents of the file 40 | # 41 | # === Returns 42 | # 43 | # [String] contents of the file 44 | # 45 | def read 46 | file.read if file.respond_to?(:read) 47 | end 48 | 49 | ## 50 | # Fetches the size of the currently stored/cached file 51 | # 52 | # === Returns 53 | # 54 | # [Integer] size of the file 55 | # 56 | def size 57 | file.respond_to?(:size) ? file.size : 0 58 | end 59 | 60 | ## 61 | # Return the size of the file when asked for its length 62 | # 63 | # === Returns 64 | # 65 | # [Integer] size of the file 66 | # 67 | # === Note 68 | # 69 | # This was added because of the way Rails handles length/size validations in 3.0.6 and above. 70 | # 71 | def length 72 | size 73 | end 74 | 75 | ## 76 | # Read the content type of the file 77 | # 78 | # === Returns 79 | # 80 | # [String] content type of the file 81 | # 82 | def content_type 83 | file.respond_to?(:content_type) ? file.content_type : nil 84 | end 85 | 86 | end # Proxy 87 | end # Uploader 88 | end # CarrierWave 89 | -------------------------------------------------------------------------------- /features/file_storage_overridden_filename.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and overriden filename 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem with an overriden filename 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader reverses the filename 9 | And an instance of that class 10 | 11 | Scenario: store a file 12 | When I store the file 'fixtures/bork.txt' 13 | Then there should be a file at 'public/uploads/txt.krob' 14 | And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' 15 | 16 | Scenario: store two files in succession 17 | When I store the file 'fixtures/bork.txt' 18 | Then there should be a file at 'public/uploads/txt.krob' 19 | And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' 20 | When I store the file 'fixtures/monkey.txt' 21 | Then there should be a file at 'public/uploads/txt.yeknom' 22 | And the file at 'public/uploads/txt.yeknom' should be identical to the file at 'fixtures/monkey.txt' 23 | 24 | Scenario: cache a file and then store it 25 | When I cache the file 'fixtures/bork.txt' 26 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 27 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 28 | And there should not be a file at 'public/uploads/txt.krob' 29 | When I store the file 30 | Then there should be a file at 'public/uploads/txt.krob' 31 | And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' 32 | 33 | Scenario: retrieving a file from cache then storing 34 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 35 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 36 | And I store the file 37 | Then there should be a file at 'public/uploads/txt.krob' 38 | And the file at 'public/uploads/txt.krob' should be identical to the file at 'fixtures/bork.txt' 39 | -------------------------------------------------------------------------------- /lib/carrierwave/processing/magic_mime_types.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | ## 5 | # This module simplifies the use of ruby-filemagic gem to intelligently 6 | # and correctly guess and set the content-type of a file. If you want 7 | # to use this, you'll need to require this file: 8 | # 9 | # require 'carrierwave/processing/magic_mime_types' 10 | # 11 | # And then include it in your uploader: 12 | # 13 | # class MyUploader < CarrierWave::Uploader::Base 14 | # include CarrierWave::MagicMimeTypes 15 | # end 16 | # 17 | # You can use the provided helper: 18 | # 19 | # class MyUploader < CarrierWave::Uploader::Base 20 | # include CarrierWave::MagicMimeTypes 21 | # 22 | # process :set_content_type 23 | # end 24 | module MagicMimeTypes 25 | extend ActiveSupport::Concern 26 | 27 | included do 28 | begin 29 | require "filemagic" 30 | rescue LoadError => e 31 | e.message << " (You may need to install the ruby-filemagic gem)" 32 | raise e 33 | end 34 | end 35 | 36 | module ClassMethods 37 | def set_content_type(override=false) 38 | process :set_content_type => override 39 | end 40 | end 41 | 42 | ## 43 | # Changes the file content_type using the ruby-filemagic gem 44 | # 45 | # === Parameters 46 | # 47 | # [override (Boolean)] wheter or not to override the file's content_type 48 | # if it is already set, false by default 49 | def set_content_type(override=false) 50 | if override || file.content_type.blank? 51 | File.open(file.path) do |fd| 52 | data = fd.read(1024) || "" 53 | new_content_type = filemagic.buffer(data) 54 | if file.respond_to?(:content_type=) 55 | file.content_type = new_content_type 56 | else 57 | file.instance_variable_set(:@content_type, new_content_type) 58 | end 59 | end 60 | end 61 | end 62 | 63 | ## 64 | # FileMagic object with the MAGIC_MIME_TYPE flag set 65 | # 66 | # @return [FileMagic] a filemagic object 67 | def filemagic 68 | @filemagic ||= FileMagic.new(FileMagic::MAGIC_MIME_TYPE) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /features/file_storage_overridden_store_dir.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and overridden store dir 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader has the store_dir overridden to 'public/monkey/llama' 9 | And an instance of that class 10 | 11 | Scenario: store a file 12 | When I store the file 'fixtures/bork.txt' 13 | Then there should be a file at 'public/monkey/llama/bork.txt' 14 | And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' 15 | 16 | Scenario: store two files in succession 17 | When I store the file 'fixtures/bork.txt' 18 | Then there should be a file at 'public/monkey/llama/bork.txt' 19 | And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' 20 | When I store the file 'fixtures/monkey.txt' 21 | Then there should be a file at 'public/monkey/llama/monkey.txt' 22 | And the file at 'public/monkey/llama/monkey.txt' should be identical to the file at 'fixtures/monkey.txt' 23 | 24 | Scenario: cache a file and then store it 25 | When I cache the file 'fixtures/bork.txt' 26 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 27 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 28 | And there should not be a file at 'public/monkey/llama/bork.txt' 29 | When I store the file 30 | Then there should be a file at 'public/monkey/llama/bork.txt' 31 | And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' 32 | 33 | Scenario: retrieving a file from cache then storing 34 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 35 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 36 | And I store the file 37 | Then there should be a file at 'public/monkey/llama/bork.txt' 38 | And the file at 'public/monkey/llama/bork.txt' should be identical to the file at 'fixtures/bork.txt' 39 | -------------------------------------------------------------------------------- /features/versions_caching_from_versions.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and versions with overridden store dir 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | Given a processor method named :upcase 9 | And that the uploader class has a version named 'thumb' which process 'upcase' 10 | And that the version 'thumb' has the store_dir overridden to 'public/monkey/llama' 11 | And that the uploader class has a version named 'small_thumb' which is based on version 'thumb' 12 | And that the version 'small_thumb' has the store_dir overridden to 'public/monkey/toro' 13 | And an instance of that class 14 | 15 | Scenario: cache a file and then store it 16 | When I cache the file 'fixtures/bork.txt' 17 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 18 | Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 19 | Then there should be a file called 'small_thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 20 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 21 | And the file called 'thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/upcased_bork.txt' 22 | And the file called 'small_thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/upcased_bork.txt' 23 | And there should not be a file at 'public/uploads/bork.txt' 24 | And there should not be a file at 'public/monkey/llama/thumb_bork.txt' 25 | And there should not be a file at 'public/monkey/toro/small_thumb_bork.txt' 26 | When I store the file 27 | Then there should be a file at 'public/uploads/bork.txt' 28 | Then there should be a file at 'public/monkey/llama/thumb_bork.txt' 29 | Then there should be a file at 'public/monkey/toro/small_thumb_bork.txt' 30 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 31 | And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/upcased_bork.txt' 32 | And the file at 'public/monkey/toro/small_thumb_bork.txt' should be identical to the file at 'fixtures/upcased_bork.txt' -------------------------------------------------------------------------------- /spec/uploader/remove_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '#remove!' do 17 | before do 18 | @file = File.open(file_path('test.jpg')) 19 | 20 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1390890634-26112-2122') 21 | 22 | @cached_file = double('a cached file') 23 | allow(@cached_file).to receive(:delete) 24 | 25 | @stored_file = double('a stored file') 26 | allow(@stored_file).to receive(:path).and_return('/path/to/somewhere') 27 | allow(@stored_file).to receive(:url).and_return('http://www.example.com') 28 | allow(@stored_file).to receive(:identifier).and_return('this-is-me') 29 | allow(@stored_file).to receive(:delete) 30 | 31 | @storage = double('a storage engine') 32 | allow(@storage).to receive(:store!).and_return(@stored_file) 33 | allow(@storage).to receive(:cache!).and_return(@cached_file) 34 | allow(@storage).to receive(:delete_dir!).with("uploads/tmp/#{CarrierWave.generate_cache_id}") 35 | 36 | allow(@uploader_class.storage).to receive(:new).and_return(@storage) 37 | @uploader.store!(@file) 38 | end 39 | 40 | it "should reset the current path" do 41 | @uploader.remove! 42 | expect(@uploader.current_path).to be_nil 43 | end 44 | 45 | it "should not be cached" do 46 | @uploader.remove! 47 | expect(@uploader).not_to be_cached 48 | end 49 | 50 | it "should reset the url" do 51 | @uploader.cache!(@file) 52 | @uploader.remove! 53 | expect(@uploader.url).to be_nil 54 | end 55 | 56 | it "should reset the identifier" do 57 | @uploader.remove! 58 | expect(@uploader.identifier).to be_nil 59 | end 60 | 61 | it "should delete the file" do 62 | expect(@stored_file).to receive(:delete) 63 | @uploader.remove! 64 | end 65 | 66 | it "should reset the cache_name" do 67 | @uploader.cache!(@file) 68 | @uploader.remove! 69 | expect(@uploader.cache_name).to be_nil 70 | end 71 | 72 | it "should do nothing when trying to remove an empty file" do 73 | expect(running { @uploader.remove! }).not_to raise_error 74 | end 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /spec/uploader/default_url_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe 'with a default url' do 17 | before do 18 | @uploader_class.class_eval do 19 | version :thumb 20 | def default_url 21 | ["http://someurl.example.com", version_name].compact.join('/') 22 | end 23 | end 24 | @uploader = @uploader_class.new 25 | end 26 | 27 | describe '#blank?' do 28 | it "should be true by default" do 29 | expect(@uploader).to be_blank 30 | end 31 | end 32 | 33 | describe '#current_path' do 34 | it "should return nil" do 35 | expect(@uploader.current_path).to be_nil 36 | end 37 | end 38 | 39 | describe '#url' do 40 | it "should return the default url" do 41 | expect(@uploader.url).to eq('http://someurl.example.com') 42 | end 43 | 44 | it "should return the default url with version when given" do 45 | expect(@uploader.url(:thumb)).to eq('http://someurl.example.com/thumb') 46 | end 47 | end 48 | 49 | describe '#cache!' do 50 | 51 | before do 52 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 53 | end 54 | 55 | it "should cache a file" do 56 | @uploader.cache!(File.open(file_path('test.jpg'))) 57 | expect(@uploader.file).to be_an_instance_of(CarrierWave::SanitizedFile) 58 | end 59 | 60 | it "should be cached" do 61 | @uploader.cache!(File.open(file_path('test.jpg'))) 62 | expect(@uploader).to be_cached 63 | end 64 | 65 | it "should no longer be blank" do 66 | @uploader.cache!(File.open(file_path('test.jpg'))) 67 | expect(@uploader).not_to be_blank 68 | end 69 | 70 | it "should set the current_path" do 71 | @uploader.cache!(File.open(file_path('test.jpg'))) 72 | expect(@uploader.current_path).to eq(public_path('uploads/tmp/1369894322-345-2255/test.jpg')) 73 | end 74 | 75 | it "should set the url" do 76 | @uploader.cache!(File.open(file_path('test.jpg'))) 77 | expect(@uploader.url).not_to eq('http://someurl.example.com') 78 | expect(@uploader.url).to eq('/uploads/tmp/1369894322-345-2255/test.jpg') 79 | end 80 | 81 | end 82 | 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /features/mount_activerecord.feature: -------------------------------------------------------------------------------- 1 | Feature: Mount an Uploader on ActiveRecord class 2 | In order to easily attach files to a form 3 | As a web developer using CarrierWave 4 | I want to mount an uploader on an ActiveRecord class 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And an activerecord class that uses the 'users' table 9 | And the uploader class is mounted on the 'avatar' column 10 | And an instance of the activerecord class 11 | 12 | Scenario: assign a file 13 | When I assign the file 'fixtures/bork.txt' to the 'avatar' column 14 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 15 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 16 | 17 | Scenario: assign a file and save the record 18 | When I assign the file 'fixtures/bork.txt' to the 'avatar' column 19 | And I save the active record 20 | Then there should be a file at 'public/uploads/bork.txt' 21 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 22 | And the url for the column 'avatar' should be '/uploads/bork.txt' 23 | 24 | Scenario: assign a file and retrieve it from cache 25 | When I assign the file 'fixtures/bork.txt' to the 'avatar' column 26 | And I retrieve the file later from the cache name for the column 'avatar' 27 | And I save the active record 28 | Then there should be a file at 'public/uploads/bork.txt' 29 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 30 | And the url for the column 'avatar' should be '/uploads/bork.txt' 31 | 32 | Scenario: store a file and retrieve it later 33 | When I assign the file 'fixtures/bork.txt' to the 'avatar' column 34 | And I retrieve the file later from the cache name for the column 'avatar' 35 | And I save the active record 36 | Then there should be a file at 'public/uploads/bork.txt' 37 | When I reload the active record 38 | Then the url for the column 'avatar' should be '/uploads/bork.txt' 39 | 40 | Scenario: store a file and delete the record 41 | When I assign the file 'fixtures/bork.txt' to the 'avatar' column 42 | And I retrieve the file later from the cache name for the column 'avatar' 43 | And I save the active record 44 | Then there should be a file at 'public/uploads/bork.txt' 45 | When I delete the active record 46 | Then there should not be a file at 'public/uploads/bork.txt' 47 | -------------------------------------------------------------------------------- /spec/storage/file_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Storage::File do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '#delete_dir!' do 17 | before do 18 | @file = File.open(file_path('test.jpg')) 19 | end 20 | 21 | context "when the directory is not empty" do 22 | before do 23 | @uploader.cache!(@file) 24 | cache_path = ::File.expand_path(File.join(@uploader.cache_dir, @uploader.cache_name), @uploader.root) 25 | @cache_id_dir = File.dirname(cache_path) 26 | @existing_file = File.join(@cache_id_dir, "exsting_file.txt") 27 | File.open(@existing_file, "wb"){|f| f << "I exist"} 28 | end 29 | 30 | it "should not delete the old cache_id" do 31 | @uploader.store! 32 | expect(File).to be_directory(@cache_id_dir) 33 | end 34 | 35 | it "should not delete other existing files in old cache_id dir" do 36 | @uploader.store! 37 | expect(File).to exist @existing_file 38 | end 39 | end 40 | end 41 | 42 | describe '#clean_cache!' do 43 | before do 44 | five_days_ago_int = 1369894322 45 | three_days_ago_int = 1370067122 46 | yesterday_int = 1370239922 47 | 48 | @cache_dir = File.expand_path(@uploader_class.cache_dir, CarrierWave.root) 49 | FileUtils.mkdir_p File.expand_path("#{five_days_ago_int}-234-2213", @cache_dir) 50 | FileUtils.mkdir_p File.expand_path("#{three_days_ago_int}-234-2213", @cache_dir) 51 | FileUtils.mkdir_p File.expand_path("#{yesterday_int}-234-2213", @cache_dir) 52 | end 53 | 54 | after { FileUtils.rm_rf(@cache_dir) } 55 | 56 | it "should clear all files older than, by default, 24 hours in the default cache directory" do 57 | Timecop.freeze(Time.at(1370261522)) do 58 | @uploader_class.clean_cached_files! 59 | end 60 | expect(Dir.glob("#{@cache_dir}/*").size).to eq(1) 61 | end 62 | 63 | it "should permit to set since how many seconds delete the cached files" do 64 | Timecop.freeze(Time.at(1370261522)) do 65 | @uploader_class.clean_cached_files!(60*60*24*4) 66 | end 67 | expect(Dir.glob("#{@cache_dir}/*").size).to eq(2) 68 | end 69 | 70 | it "should be aliased on the CarrierWave module" do 71 | Timecop.freeze(Time.at(1370261522)) do 72 | CarrierWave.clean_cached_files! 73 | end 74 | expect(Dir.glob("#{@cache_dir}/*").size).to eq(1) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require "carrierwave/uploader/configuration" 4 | require "carrierwave/uploader/callbacks" 5 | require "carrierwave/uploader/proxy" 6 | require "carrierwave/uploader/url" 7 | require "carrierwave/uploader/mountable" 8 | require "carrierwave/uploader/cache" 9 | require "carrierwave/uploader/store" 10 | require "carrierwave/uploader/download" 11 | require "carrierwave/uploader/remove" 12 | require "carrierwave/uploader/extension_whitelist" 13 | require "carrierwave/uploader/extension_blacklist" 14 | require "carrierwave/uploader/magic_mime_whitelist" 15 | require "carrierwave/uploader/magic_mime_blacklist" 16 | require "carrierwave/uploader/file_size" 17 | require "carrierwave/uploader/processing" 18 | require "carrierwave/uploader/versions" 19 | require "carrierwave/uploader/default_url" 20 | 21 | require "carrierwave/uploader/serialization" 22 | 23 | module CarrierWave 24 | 25 | ## 26 | # See CarrierWave::Uploader::Base 27 | # 28 | module Uploader 29 | 30 | ## 31 | # An uploader is a class that allows you to easily handle the caching and storage of 32 | # uploaded files. Please refer to the README for configuration options. 33 | # 34 | # Once you have an uploader you can use it in isolation: 35 | # 36 | # my_uploader = MyUploader.new 37 | # my_uploader.cache!(File.open(path_to_file)) 38 | # my_uploader.retrieve_from_store!('monkey.png') 39 | # 40 | # Alternatively, you can mount it on an ORM or other persistence layer, with 41 | # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper 42 | # these are *very* simple (they are only a dozen lines of code), so adding your own should 43 | # be trivial. 44 | # 45 | class Base 46 | attr_reader :file 47 | 48 | include CarrierWave::Uploader::Configuration 49 | include CarrierWave::Uploader::Callbacks 50 | include CarrierWave::Uploader::Proxy 51 | include CarrierWave::Uploader::Url 52 | include CarrierWave::Uploader::Mountable 53 | include CarrierWave::Uploader::Cache 54 | include CarrierWave::Uploader::Store 55 | include CarrierWave::Uploader::Download 56 | include CarrierWave::Uploader::Remove 57 | include CarrierWave::Uploader::ExtensionWhitelist 58 | include CarrierWave::Uploader::ExtensionBlacklist 59 | include CarrierWave::Uploader::FileSize 60 | include CarrierWave::Uploader::Processing 61 | include CarrierWave::Uploader::Versions 62 | include CarrierWave::Uploader::DefaultUrl 63 | include CarrierWave::Uploader::Serialization 64 | end # Base 65 | 66 | end # Uploader 67 | end # CarrierWave 68 | -------------------------------------------------------------------------------- /spec/processing/mime_types_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::MimeTypes do 6 | 7 | before do 8 | @klass = Class.new(CarrierWave::Uploader::Base) do 9 | attr_accessor :content_type 10 | include CarrierWave::MimeTypes 11 | end 12 | @instance = @klass.new 13 | FileUtils.cp(file_path('landscape.jpg'), file_path('landscape_copy.jpg')) 14 | allow(@instance).to receive(:original_filename).and_return file_path('landscape_copy.jpg') 15 | allow(@instance).to receive(:file).and_return CarrierWave::SanitizedFile.new(file_path('landscape_copy.jpg')) 16 | @file = @instance.file 17 | end 18 | 19 | after do 20 | FileUtils.rm(file_path('landscape_copy.jpg')) 21 | end 22 | 23 | describe '#set_content_type' do 24 | 25 | it "does not set content_type if already set" do 26 | @instance.file.content_type = 'image/jpeg' 27 | expect(@instance.file).not_to receive(:content_type=) 28 | @instance.set_content_type 29 | end 30 | 31 | it "set content_type if content_type is nil" do 32 | pending 'This spec is deprecated because Proxy now read content type itself.' 33 | @instance.file.content_type = nil 34 | expect(@instance.file).to receive(:content_type=).with('image/jpeg') 35 | @instance.set_content_type 36 | end 37 | 38 | it "set content_type if content_type is empty" do 39 | @instance.file.content_type = '' 40 | expect(@instance.file).to receive(:content_type=).with('image/jpeg') 41 | @instance.set_content_type 42 | end 43 | 44 | %w[ application/octet-stream binary/octet-stream ].each do |type| 45 | it "sets content_type if content_type is generic (#{type})" do 46 | @instance.file.content_type = type 47 | expect(@instance.file).to receive(:content_type=).with('image/jpeg') 48 | @instance.set_content_type 49 | end 50 | end 51 | 52 | it "sets content_type if override is true" do 53 | @instance.file.content_type = 'image/jpeg' 54 | expect(@instance.file).to receive(:content_type=).with('image/jpeg') 55 | @instance.set_content_type(true) 56 | end 57 | 58 | end 59 | 60 | describe "test errors" do 61 | context "invalid mime type" do 62 | before do 63 | @instance.file.content_type = nil 64 | # TODO: somehow force a ::MIME::InvalidContentType error when set_content_type is called. 65 | end 66 | 67 | it "should raise a MIME::InvalidContentType error" do 68 | # lambda {@instance.set_content_type}.should raise_exception(::MIME::InvalidContentType, /^Failed to process file with MIME::Types, maybe not valid content-type\?/) 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /features/file_storage_reversing_processor.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and a processor that reverses the file 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And an instance of that class 9 | And the class has a method called 'reverse' that reverses the contents of a file 10 | And the class will process 'reverse' 11 | 12 | Scenario: store a file 13 | When I store the file 'fixtures/bork.txt' 14 | Then there should be a file at 'public/uploads/bork.txt' 15 | And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' 16 | And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' 17 | 18 | Scenario: store two files in succession 19 | When I store the file 'fixtures/bork.txt' 20 | Then there should be a file at 'public/uploads/bork.txt' 21 | And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' 22 | And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' 23 | When I store the file 'fixtures/monkey.txt' 24 | Then there should be a file at 'public/uploads/monkey.txt' 25 | And the file at 'public/uploads/monkey.txt' should not be identical to the file at 'fixtures/monkey.txt' 26 | And the file at 'public/uploads/monkey.txt' should be the reverse of the file at 'fixtures/monkey.txt' 27 | 28 | Scenario: cache a file and then store it 29 | When I cache the file 'fixtures/bork.txt' 30 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 31 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should not be identical to the file at 'fixtures/bork.txt' 32 | And there should not be a file at 'public/uploads/bork.txt' 33 | When I store the file 34 | Then there should be a file at 'public/uploads/bork.txt' 35 | And the file at 'public/uploads/bork.txt' should not be identical to the file at 'fixtures/bork.txt' 36 | And the file at 'public/uploads/bork.txt' should be the reverse of the file at 'fixtures/bork.txt' 37 | 38 | Scenario: retrieving a file from cache then storing 39 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 40 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 41 | And I store the file 42 | Then there should be a file at 'public/uploads/bork.txt' 43 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 44 | -------------------------------------------------------------------------------- /lib/carrierwave/processing/mime_types.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | 5 | ## 6 | # This module simplifies the use of the mime-types gem to intelligently 7 | # guess and set the content-type of a file. If you want to use this, you'll 8 | # need to require this file: 9 | # 10 | # require 'carrierwave/processing/mime_types' 11 | # 12 | # And then include it in your uploader: 13 | # 14 | # class MyUploader < CarrierWave::Uploader::Base 15 | # include CarrierWave::MimeTypes 16 | # end 17 | # 18 | # You can now use the provided helper: 19 | # 20 | # class MyUploader < CarrierWave::Uploader::Base 21 | # include CarrierWave::MimeTypes 22 | # 23 | # process :set_content_type 24 | # end 25 | # 26 | module MimeTypes 27 | extend ActiveSupport::Concern 28 | 29 | included do 30 | CarrierWave::Utilities::Deprecation.new "0.11.0", "CarrierWave::MimeTypes is deprecated and will be removed in the future, get the content_type from the SanitizedFile object directly." 31 | begin 32 | # Use mime/types/columnar if available, for reduced memory usage 33 | require 'mime/types/columnar' 34 | rescue LoadError 35 | require "mime/types" 36 | rescue LoadError => e 37 | e.message << " (You may need to install the mime-types gem)" 38 | raise e 39 | end 40 | end 41 | 42 | module ClassMethods 43 | def set_content_type(override=false) 44 | process :set_content_type => override 45 | end 46 | end 47 | 48 | GENERIC_CONTENT_TYPES = %w[application/octet-stream binary/octet-stream] 49 | 50 | def generic_content_type? 51 | GENERIC_CONTENT_TYPES.include? file.content_type 52 | end 53 | 54 | ## 55 | # Changes the file content_type using the mime-types gem 56 | # 57 | # === Parameters 58 | # 59 | # [override (Boolean)] whether or not to override the file's content_type 60 | # if it is already set and not a generic content-type, 61 | # false by default 62 | # 63 | def set_content_type(override=false) 64 | if override || file.content_type.blank? || generic_content_type? 65 | new_content_type = ::MIME::Types.type_for(current_path).first.to_s 66 | if file.respond_to?(:content_type=) 67 | file.content_type = new_content_type 68 | else 69 | file.instance_variable_set(:@content_type, new_content_type) 70 | end 71 | end 72 | rescue ::MIME::InvalidContentType => e 73 | raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.mime_types_processing_error", :e => e) 74 | end 75 | 76 | end # MimeTypes 77 | end # CarrierWave 78 | -------------------------------------------------------------------------------- /features/versions_overriden_store_dir.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and versions with overridden store dir 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader class has a version named 'thumb' 9 | And that the version 'thumb' has the store_dir overridden to 'public/monkey/llama' 10 | And an instance of that class 11 | 12 | Scenario: store a file 13 | When I store the file 'fixtures/bork.txt' 14 | Then there should be a file at 'public/uploads/bork.txt' 15 | Then there should be a file at 'public/monkey/llama/thumb_bork.txt' 16 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 17 | And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 18 | 19 | Scenario: cache a file and then store it 20 | When I cache the file 'fixtures/bork.txt' 21 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 22 | Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 23 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 24 | And the file called 'thumb_bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 25 | And there should not be a file at 'public/uploads/bork.txt' 26 | And there should not be a file at 'public/monkey/llama/thumb_bork.txt' 27 | When I store the file 28 | Then there should be a file at 'public/uploads/bork.txt' 29 | Then there should be a file at 'public/monkey/llama/thumb_bork.txt' 30 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 31 | And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 32 | 33 | Scenario: retrieving a file from cache then storing 34 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 35 | Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' 36 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 37 | And I store the file 38 | Then there should be a file at 'public/uploads/bork.txt' 39 | Then there should be a file at 'public/monkey/llama/thumb_bork.txt' 40 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 41 | And the file at 'public/monkey/llama/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' 42 | -------------------------------------------------------------------------------- /lib/carrierwave/validations/active_model.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'active_model/validator' 4 | require 'active_support/concern' 5 | 6 | module CarrierWave 7 | 8 | # == Active Model Presence Validator 9 | module Validations 10 | module ActiveModel 11 | extend ActiveSupport::Concern 12 | 13 | class ProcessingValidator < ::ActiveModel::EachValidator 14 | 15 | def validate_each(record, attribute, value) 16 | if e = record.send("#{attribute}_processing_error") 17 | message = (e.message == e.class.to_s) ? :carrierwave_processing_error : e.message 18 | record.errors.add(attribute, message) 19 | end 20 | end 21 | end 22 | 23 | class IntegrityValidator < ::ActiveModel::EachValidator 24 | 25 | def validate_each(record, attribute, value) 26 | if e = record.send("#{attribute}_integrity_error") 27 | message = (e.message == e.class.to_s) ? :carrierwave_integrity_error : e.message 28 | record.errors.add(attribute, message) 29 | end 30 | end 31 | end 32 | 33 | class DownloadValidator < ::ActiveModel::EachValidator 34 | 35 | def validate_each(record, attribute, value) 36 | if e = record.send("#{attribute}_download_error") 37 | message = (e.message == e.class.to_s) ? :carrierwave_download_error : e.message 38 | record.errors.add(attribute, message) 39 | end 40 | end 41 | end 42 | 43 | module HelperMethods 44 | 45 | ## 46 | # Makes the record invalid if the file couldn't be uploaded due to an integrity error 47 | # 48 | # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) 49 | # 50 | def validates_integrity_of(*attr_names) 51 | validates_with IntegrityValidator, _merge_attributes(attr_names) 52 | end 53 | 54 | ## 55 | # Makes the record invalid if the file couldn't be processed (assuming the process failed 56 | # with a CarrierWave::ProcessingError) 57 | # 58 | # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) 59 | # 60 | def validates_processing_of(*attr_names) 61 | validates_with ProcessingValidator, _merge_attributes(attr_names) 62 | end 63 | # 64 | ## 65 | # Makes the record invalid if the remote file couldn't be downloaded 66 | # 67 | # Accepts the usual parameters for validations in Rails (:if, :unless, etc...) 68 | # 69 | def validates_download_of(*attr_names) 70 | validates_with DownloadValidator, _merge_attributes(attr_names) 71 | end 72 | end 73 | 74 | included do 75 | extend HelperMethods 76 | include HelperMethods 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/processing.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Processing 6 | extend ActiveSupport::Concern 7 | 8 | include CarrierWave::Uploader::Callbacks 9 | 10 | included do 11 | class_attribute :processors, :instance_writer => false 12 | self.processors = [] 13 | 14 | before :cache, :process! 15 | end 16 | 17 | module ClassMethods 18 | 19 | ## 20 | # Adds a processor callback which applies operations as a file is uploaded. 21 | # The argument may be the name of any method of the uploader, expressed as a symbol, 22 | # or a list of such methods, or a hash where the key is a method and the value is 23 | # an array of arguments to call the method with 24 | # 25 | # === Parameters 26 | # 27 | # args (*Symbol, Hash{Symbol => Array[]}) 28 | # 29 | # === Examples 30 | # 31 | # class MyUploader < CarrierWave::Uploader::Base 32 | # 33 | # process :sepiatone, :vignette 34 | # process :scale => [200, 200] 35 | # process :scale => [200, 200], :if => :image? 36 | # process :sepiatone, :if => :image? 37 | # 38 | # def sepiatone 39 | # ... 40 | # end 41 | # 42 | # def vignette 43 | # ... 44 | # end 45 | # 46 | # def scale(height, width) 47 | # ... 48 | # end 49 | # 50 | # def image? 51 | # ... 52 | # end 53 | # 54 | # end 55 | # 56 | def process(*args) 57 | new_processors = args.inject({}) do |hash, arg| 58 | arg = { arg => [] } unless arg.is_a?(Hash) 59 | hash.merge!(arg) 60 | end 61 | 62 | condition = new_processors.delete(:if) 63 | new_processors.each do |processor, processor_args| 64 | self.processors += [[processor, processor_args, condition]] 65 | end 66 | end 67 | 68 | end # ClassMethods 69 | 70 | ## 71 | # Apply all process callbacks added through CarrierWave.process 72 | # 73 | def process!(new_file=nil) 74 | return unless enable_processing 75 | 76 | self.class.processors.each do |method, args, condition| 77 | if(condition) 78 | if condition.respond_to?(:call) 79 | next unless condition.call(self, :args => args, :method => method, :file => new_file) 80 | else 81 | next unless self.send(condition, new_file) 82 | end 83 | end 84 | self.send(method, *args) 85 | end 86 | end 87 | 88 | end # Processing 89 | end # Uploader 90 | end # CarrierWave 91 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/magic_mime_whitelist.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | 6 | ## 7 | # This modules validates the content type of a file with the use of 8 | # ruby-filemagic gem and a whitelist regular expression. If you want 9 | # to use this, you'll need to require this file: 10 | # 11 | # require 'carrierwave/uploader/magic_mime_whitelist' 12 | # 13 | # And then include it in your uploader: 14 | # 15 | # class MyUploader < CarrierWave::Uploader::Base 16 | # include CarrierWave::Uploader::MagicMimeWhitelist 17 | # 18 | # def whitelist_mime_type_pattern 19 | # /image\// 20 | # end 21 | # end 22 | # 23 | module MagicMimeWhitelist 24 | extend ActiveSupport::Concern 25 | 26 | included do 27 | begin 28 | require "filemagic" 29 | rescue LoadError => e 30 | e.message << " (You may need to install the ruby-filemagic gem)" 31 | raise e 32 | end 33 | 34 | before :cache, :check_whitelist_pattern! 35 | end 36 | 37 | ## 38 | # Override this method in your uploader to provide a white list pattern (regexp) 39 | # of content-types which are allowed to be uploaded. 40 | # Compares the file's content-type. 41 | # 42 | # === Returns 43 | # 44 | # [Regexp] a white list regexp to match the content_type 45 | # 46 | # === Examples 47 | # 48 | # def whitelist_mime_type_pattern 49 | # /(text|application)\/json/ 50 | # end 51 | # 52 | def whitelist_mime_type_pattern; end 53 | 54 | private 55 | 56 | def check_whitelist_pattern!(new_file) 57 | return if whitelist_mime_type_pattern.nil? 58 | 59 | content_type = extract_content_type(new_file) 60 | 61 | if !content_type.match(whitelist_mime_type_pattern) 62 | raise CarrierWave::IntegrityError, 63 | I18n.translate(:"errors.messages.mime_type_pattern_white_list_error", 64 | :content_type => content_type) 65 | end 66 | end 67 | 68 | ## 69 | # Extracts the content type of the given file 70 | # 71 | # === Returns 72 | # 73 | # [String] the extracted content type 74 | # 75 | def extract_content_type(new_file) 76 | content_type = nil 77 | 78 | File.open(new_file.path) do |fd| 79 | data = fd.read(1024) || "" 80 | content_type = filemagic.buffer(data) 81 | end 82 | 83 | content_type 84 | end 85 | 86 | ## 87 | # FileMagic object with the MAGIC_MIME_TYPE flag set 88 | # 89 | # @return [FileMagic] a filemagic object 90 | def filemagic 91 | @filemagic ||= FileMagic.new(FileMagic::MAGIC_MIME_TYPE) 92 | end 93 | 94 | end # MagicMimeWhiteList 95 | end # Uploader 96 | end # CarrierWave 97 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/magic_mime_blacklist.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | 6 | ## 7 | # This modules validates the content type of a file with the use of 8 | # ruby-filemagic gem and a blacklist regular expression. If you want 9 | # to use this, you'll need to require this file: 10 | # 11 | # require 'carrierwave/uploader/magic_mime_blacklist' 12 | # 13 | # And then include it in your uploader: 14 | # 15 | # class MyUploader < CarrierWave::Uploader::Base 16 | # include CarrierWave::Uploader::MagicMimeBlacklist 17 | # 18 | # def blacklist_mime_type_pattern 19 | # /image\// 20 | # end 21 | # end 22 | # 23 | module MagicMimeBlacklist 24 | extend ActiveSupport::Concern 25 | 26 | included do 27 | begin 28 | require "filemagic" 29 | rescue LoadError => e 30 | e.message << " (You may need to install the ruby-filemagic gem)" 31 | raise e 32 | end 33 | 34 | before :cache, :check_blacklist_pattern! 35 | end 36 | 37 | ## 38 | # Override this method in your uploader to provide a black list pattern (regexp) 39 | # of content-types which are prohibited to be uploaded. 40 | # Compares the file's content-type. 41 | # 42 | # === Returns 43 | # 44 | # [Regexp] a black list regexp to match the content_type 45 | # 46 | # === Examples 47 | # 48 | # def blacklist_mime_type_pattern 49 | # /(text|application)\/json/ 50 | # end 51 | # 52 | def blacklist_mime_type_pattern; end 53 | 54 | private 55 | 56 | def check_blacklist_pattern!(new_file) 57 | return if blacklist_mime_type_pattern.nil? 58 | 59 | content_type = extract_content_type(new_file) 60 | 61 | if content_type.match(blacklist_mime_type_pattern) 62 | raise CarrierWave::IntegrityError, 63 | I18n.translate(:"errors.messages.mime_type_pattern_black_list_error", 64 | :content_type => content_type) 65 | end 66 | end 67 | 68 | ## 69 | # Extracts the content type of the given file 70 | # 71 | # === Returns 72 | # 73 | # [String] the extracted content type 74 | # 75 | def extract_content_type(new_file) 76 | content_type = nil 77 | 78 | File.open(new_file.path) do |fd| 79 | data = fd.read(1024) || "" 80 | content_type = filemagic.buffer(data) 81 | end 82 | 83 | content_type 84 | end 85 | 86 | ## 87 | # FileMagic object with the MAGIC_MIME_TYPE flag set 88 | # 89 | # @return [FileMagic] a filemagic object 90 | def filemagic 91 | @filemagic ||= FileMagic.new(FileMagic::MAGIC_MIME_TYPE) 92 | end 93 | 94 | end # MagicMimeblackList 95 | end # Uploader 96 | end # CarrierWave 97 | -------------------------------------------------------------------------------- /spec/uploader/overrides_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader_class.configure do |config| 10 | 11 | config.fog_credentials = { 12 | :provider => 'AWS', # required 13 | :aws_access_key_id => 'XXXX', # required 14 | :aws_secret_access_key => 'YYYY', # required 15 | :region => 'us-east-1' # optional, defaults to 'us-east-1' 16 | } 17 | 18 | config.fog_directory = "defaultbucket" 19 | end 20 | 21 | @uploader = @uploader_class.new 22 | @uploader_overridden = @uploader_class.new 23 | @uploader_overridden.fog_credentials = { 24 | :provider => 'AWS', # required 25 | :aws_access_key_id => 'ZZZZ', # required 26 | :aws_secret_access_key => 'AAAA', # required 27 | :region => 'us-east-2' # optional, defaults to 'us-east-1' 28 | } 29 | @uploader_overridden.fog_public = false 30 | end 31 | 32 | describe 'fog_credentials' do 33 | it 'should reflect the standard value if no override done' do 34 | expect(@uploader.fog_credentials).to be_a(Hash) 35 | expect(@uploader.fog_credentials[:provider]).to be_eql('AWS') 36 | expect(@uploader.fog_credentials[:aws_access_key_id]).to be_eql('XXXX') 37 | expect(@uploader.fog_credentials[:aws_secret_access_key]).to be_eql('YYYY') 38 | expect(@uploader.fog_credentials[:region]).to be_eql('us-east-1') 39 | end 40 | 41 | it 'should reflect the new values in uploader class with override' do 42 | expect(@uploader_overridden.fog_credentials).to be_a(Hash) 43 | expect(@uploader_overridden.fog_credentials[:provider]).to be_eql('AWS') 44 | expect(@uploader_overridden.fog_credentials[:aws_access_key_id]).to be_eql('ZZZZ') 45 | expect(@uploader_overridden.fog_credentials[:aws_secret_access_key]).to be_eql('AAAA') 46 | expect(@uploader_overridden.fog_credentials[:region]).to be_eql('us-east-2') 47 | end 48 | end 49 | 50 | describe 'fog_directory' do 51 | it 'should reflect the standard value if no override done' do 52 | expect(@uploader.fog_directory).to be_eql('defaultbucket') 53 | end 54 | 55 | it 'should reflect the standard value in overridden object because property is not overridden' do 56 | expect(@uploader_overridden.fog_directory).to be_eql('defaultbucket') 57 | end 58 | end 59 | 60 | describe 'fog_public' do 61 | it 'should reflect the standard value if no override done' do 62 | expect(@uploader.fog_public).to be_eql(true) 63 | end 64 | 65 | it 'should reflect the standard value in overridden object because property is not overridden' do 66 | expect(@uploader_overridden.fog_public).to be_eql(false) 67 | end 68 | end 69 | end -------------------------------------------------------------------------------- /lib/carrierwave/uploader/store.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Uploader 5 | module Store 6 | extend ActiveSupport::Concern 7 | 8 | include CarrierWave::Uploader::Callbacks 9 | include CarrierWave::Uploader::Configuration 10 | include CarrierWave::Uploader::Cache 11 | 12 | ## 13 | # Override this in your Uploader to change the filename. 14 | # 15 | # Be careful using record ids as filenames. If the filename is stored in the database 16 | # the record id will be nil when the filename is set. Don't use record ids unless you 17 | # understand this limitation. 18 | # 19 | # Do not use the version_name in the filename, as it will prevent versions from being 20 | # loaded correctly. 21 | # 22 | # === Returns 23 | # 24 | # [String] a filename 25 | # 26 | def filename 27 | @filename 28 | end 29 | 30 | ## 31 | # Calculates the path where the file should be stored. If +for_file+ is given, it will be 32 | # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed. 33 | # 34 | # === Parameters 35 | # 36 | # [for_file (String)] name of the file 37 | # 38 | # === Returns 39 | # 40 | # [String] the store path 41 | # 42 | def store_path(for_file=filename) 43 | File.join([store_dir, full_filename(for_file)].compact) 44 | end 45 | 46 | ## 47 | # Stores the file by passing it to this Uploader's storage engine. 48 | # 49 | # If new_file is omitted, a previously cached file will be stored. 50 | # 51 | # === Parameters 52 | # 53 | # [new_file (File, IOString, Tempfile)] any kind of file object 54 | # 55 | def store!(new_file=nil) 56 | cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?) 57 | if !cache_only and @file and @cache_id 58 | with_callbacks(:store, new_file) do 59 | new_file = storage.store!(@file) 60 | if delete_tmp_file_after_storage 61 | @file.delete unless move_to_store 62 | cache_storage.delete_dir!(cache_path(nil)) 63 | end 64 | @file = new_file 65 | @cache_id = nil 66 | end 67 | end 68 | end 69 | 70 | ## 71 | # Retrieves the file from the storage. 72 | # 73 | # === Parameters 74 | # 75 | # [identifier (String)] uniquely identifies the file to retrieve 76 | # 77 | def retrieve_from_store!(identifier) 78 | with_callbacks(:retrieve_from_store, identifier) do 79 | @file = storage.retrieve!(identifier) 80 | end 81 | end 82 | 83 | private 84 | 85 | def full_filename(for_file) 86 | for_file 87 | end 88 | 89 | def storage 90 | @storage ||= self.class.storage.new(self) 91 | end 92 | 93 | end # Store 94 | end # Uploader 95 | end # CarrierWave 96 | -------------------------------------------------------------------------------- /lib/carrierwave.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'fileutils' 4 | require 'active_support/core_ext/object/blank' 5 | require 'active_support/core_ext/class/attribute' 6 | require 'active_support/concern' 7 | 8 | module CarrierWave 9 | 10 | class << self 11 | attr_accessor :root, :base_path 12 | attr_writer :tmp_path 13 | 14 | def configure(&block) 15 | CarrierWave::Uploader::Base.configure(&block) 16 | end 17 | 18 | def clean_cached_files!(seconds=60*60*24) 19 | CarrierWave::Uploader::Base.clean_cached_files!(seconds) 20 | end 21 | 22 | def tmp_path 23 | @tmp_path ||= File.expand_path(File.join('..', 'tmp'), root) 24 | end 25 | end 26 | 27 | end 28 | 29 | if defined?(Merb) 30 | 31 | CarrierWave.root = Merb.dir_for(:public) 32 | Merb::BootLoader.before_app_loads do 33 | # Setup path for uploaders and load all of them before classes are loaded 34 | Merb.push_path(:uploaders, Merb.root / 'app' / 'uploaders', '*.rb') 35 | Dir.glob(File.join(Merb.load_paths[:uploaders])).each {|f| require f } 36 | end 37 | 38 | elsif defined?(Rails) 39 | 40 | module CarrierWave 41 | class Railtie < Rails::Railtie 42 | initializer "carrierwave.setup_paths" do |app| 43 | CarrierWave.root = Rails.root.join(Rails.public_path).to_s 44 | CarrierWave.base_path = ENV['RAILS_RELATIVE_URL_ROOT'] 45 | 46 | pattern = CarrierWave::Railtie.locales_pattern_from app.config.i18n.available_locales 47 | 48 | files = Dir[File.join(File.dirname(__FILE__), 'carrierwave', 'locale', "#{pattern}.yml")] 49 | # Loads the Carrierwave locale files before the Rails application locales 50 | # letting the Rails application overrite the carrierwave locale defaults 51 | I18n.load_path = files.concat I18n.load_path 52 | end 53 | 54 | initializer "carrierwave.active_record" do 55 | ActiveSupport.on_load :active_record do 56 | require 'carrierwave/orm/activerecord' 57 | end 58 | end 59 | 60 | private 61 | 62 | def self.locales_pattern_from(args) 63 | array = Array(args || []) 64 | array.blank? ? '*' : "{#{array.join ','}}" 65 | end 66 | end 67 | end 68 | 69 | elsif defined?(Sinatra) 70 | if defined?(Padrino) && defined?(PADRINO_ROOT) 71 | CarrierWave.root = File.join(PADRINO_ROOT, "public") 72 | else 73 | 74 | CarrierWave.root = if Sinatra::Application.respond_to?(:public_folder) 75 | # Sinatra >= 1.3 76 | Sinatra::Application.public_folder 77 | else 78 | # Sinatra < 1.3 79 | Sinatra::Application.public 80 | end 81 | end 82 | end 83 | 84 | require "carrierwave/utilities" 85 | require "carrierwave/error" 86 | require "carrierwave/sanitized_file" 87 | require "carrierwave/mounter" 88 | require "carrierwave/mount" 89 | require "carrierwave/processing" 90 | require "carrierwave/version" 91 | require "carrierwave/storage" 92 | require "carrierwave/uploader" 93 | require "carrierwave/compatibility/paperclip" 94 | require "carrierwave/test/matchers" 95 | -------------------------------------------------------------------------------- /spec/uploader/extension_whitelist_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '#cache!' do 17 | 18 | before do 19 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 20 | end 21 | 22 | it "should not raise an integrity error if there is no white list" do 23 | allow(@uploader).to receive(:extension_white_list).and_return(nil) 24 | expect(running { 25 | @uploader.cache!(File.open(file_path('test.jpg'))) 26 | }).not_to raise_error 27 | end 28 | 29 | it "should not raise an integrity error if there is a white list and the file is on it" do 30 | allow(@uploader).to receive(:extension_white_list).and_return(%w(jpg gif png)) 31 | expect(running { 32 | @uploader.cache!(File.open(file_path('test.jpg'))) 33 | }).not_to raise_error 34 | end 35 | 36 | it "should raise an integrity error if there is a white list and the file is not on it" do 37 | allow(@uploader).to receive(:extension_white_list).and_return(%w(txt doc xls)) 38 | expect(running { 39 | @uploader.cache!(File.open(file_path('test.jpg'))) 40 | }).to raise_error(CarrierWave::IntegrityError) 41 | end 42 | 43 | it "should raise an integrity error if there is a white list and the file is not on it, using start of string matcher" do 44 | allow(@uploader).to receive(:extension_white_list).and_return(%w(txt)) 45 | expect(running { 46 | @uploader.cache!(File.open(file_path('bork.ttxt'))) 47 | }).to raise_error(CarrierWave::IntegrityError) 48 | end 49 | 50 | it "should raise an integrity error if there is a white list and the file is not on it, using end of string matcher" do 51 | allow(@uploader).to receive(:extension_white_list).and_return(%w(txt)) 52 | expect(running { 53 | @uploader.cache!(File.open(file_path('bork.txtt'))) 54 | }).to raise_error(CarrierWave::IntegrityError) 55 | end 56 | 57 | it "should compare white list in a case insensitive manner when capitalized extension provided" do 58 | allow(@uploader).to receive(:extension_white_list).and_return(%w(jpg gif png)) 59 | expect(running { 60 | @uploader.cache!(File.open(file_path('case.JPG'))) 61 | }).not_to raise_error 62 | end 63 | 64 | it "should compare white list in a case insensitive manner when lowercase extension provided" do 65 | allow(@uploader).to receive(:extension_white_list).and_return(%w(JPG GIF PNG)) 66 | expect(running { 67 | @uploader.cache!(File.open(file_path('test.jpg'))) 68 | }).not_to raise_error 69 | end 70 | 71 | it "should accept and check regular expressions" do 72 | allow(@uploader).to receive(:extension_white_list).and_return([/jpe?g/, 'gif', 'png']) 73 | expect(running { 74 | @uploader.cache!(File.open(file_path('test.jpeg'))) 75 | }).not_to raise_error 76 | end 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/download.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'open-uri' 4 | 5 | module CarrierWave 6 | module Uploader 7 | module Download 8 | extend ActiveSupport::Concern 9 | 10 | include CarrierWave::Uploader::Callbacks 11 | include CarrierWave::Uploader::Configuration 12 | include CarrierWave::Uploader::Cache 13 | 14 | class RemoteFile 15 | def initialize(uri) 16 | @uri = uri 17 | end 18 | 19 | def original_filename 20 | filename = filename_from_header || File.basename(file.base_uri.path) 21 | mime_type = MIME::Types[file.content_type].first 22 | unless File.extname(filename).present? || mime_type.blank? 23 | filename = "#{filename}.#{mime_type.extensions.first}" 24 | end 25 | filename 26 | end 27 | 28 | def respond_to?(*args) 29 | super or file.respond_to?(*args) 30 | end 31 | 32 | def http? 33 | @uri.scheme =~ /^https?$/ 34 | end 35 | 36 | private 37 | 38 | def file 39 | if @file.blank? 40 | @file = Kernel.open(@uri.to_s, "User-Agent" => "CarrierWave/#{CarrierWave::VERSION}") 41 | @file = @file.is_a?(String) ? StringIO.new(@file) : @file 42 | end 43 | @file 44 | 45 | rescue StandardError => e 46 | raise CarrierWave::DownloadError, "could not download file: #{e.message}" 47 | end 48 | 49 | def filename_from_header 50 | if file.meta.include? 'content-disposition' 51 | match = file.meta['content-disposition'].match(/filename="?([^"]+)/) 52 | return match[1] unless match.nil? || match[1].empty? 53 | end 54 | end 55 | 56 | def method_missing(*args, &block) 57 | file.send(*args, &block) 58 | end 59 | end 60 | 61 | ## 62 | # Caches the file by downloading it from the given URL. 63 | # 64 | # === Parameters 65 | # 66 | # [url (String)] The URL where the remote file is stored 67 | # 68 | def download!(uri) 69 | processed_uri = process_uri(uri) 70 | file = RemoteFile.new(processed_uri) 71 | raise CarrierWave::DownloadError, "trying to download a file which is not served over HTTP" unless file.http? 72 | cache!(file) 73 | end 74 | 75 | ## 76 | # Processes the given URL by parsing and escaping it. Public to allow overriding. 77 | # 78 | # === Parameters 79 | # 80 | # [url (String)] The URL where the remote file is stored 81 | # 82 | def process_uri(uri) 83 | URI.parse(uri) 84 | rescue URI::InvalidURIError 85 | uri_parts = uri.split('?') 86 | # regexp from Ruby's URI::Parser#regexp[:UNSAFE], with [] specifically removed 87 | encoded_uri = URI.encode(uri_parts.shift, /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,]/) 88 | encoded_uri << '?' << URI.encode(uri_parts.join('?')) if uri_parts.any? 89 | URI.parse(encoded_uri) rescue raise CarrierWave::DownloadError, "couldn't parse URL" 90 | end 91 | 92 | end # Download 93 | end # Uploader 94 | end # CarrierWave 95 | -------------------------------------------------------------------------------- /spec/uploader/extension_blacklist_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | before do 7 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 8 | @uploader = @uploader_class.new 9 | end 10 | 11 | after do 12 | FileUtils.rm_rf(public_path) 13 | end 14 | 15 | describe '#cache!' do 16 | 17 | before do 18 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 19 | end 20 | 21 | it "should not raise an integrity error if there is no black list" do 22 | allow(@uploader).to receive(:extension_black_list).and_return(nil) 23 | expect(running { 24 | @uploader.cache!(File.open(file_path('test.jpg'))) 25 | }).not_to raise_error 26 | end 27 | 28 | it "should raise an integrity error if there is a black list and the file is on it" do 29 | allow(@uploader).to receive(:extension_black_list).and_return(%w(jpg gif png)) 30 | expect(running { 31 | @uploader.cache!(File.open(file_path('test.jpg'))) 32 | }).to raise_error(CarrierWave::IntegrityError) 33 | end 34 | 35 | it "should not raise an integrity error if there is a black list and the file is not on it" do 36 | allow(@uploader).to receive(:extension_black_list).and_return(%w(txt doc xls)) 37 | expect(running { 38 | @uploader.cache!(File.open(file_path('test.jpg'))) 39 | }).not_to raise_error 40 | end 41 | 42 | it "should not raise an integrity error if there is a black list and the file is not on it, using start of string matcher" do 43 | allow(@uploader).to receive(:extension_black_list).and_return(%w(txt)) 44 | expect(running { 45 | @uploader.cache!(File.open(file_path('bork.ttxt'))) 46 | }).not_to raise_error 47 | end 48 | 49 | it "should not raise an integrity error if there is a black list and the file is not on it, using end of string matcher" do 50 | allow(@uploader).to receive(:extension_black_list).and_return(%w(txt)) 51 | expect(running { 52 | @uploader.cache!(File.open(file_path('bork.txtt'))) 53 | }).not_to raise_error 54 | end 55 | 56 | it "should compare black list in a case insensitive manner when capitalized extension provided" do 57 | allow(@uploader).to receive(:extension_black_list).and_return(%w(jpg gif png)) 58 | expect(running { 59 | @uploader.cache!(File.open(file_path('case.JPG'))) 60 | }).to raise_error(CarrierWave::IntegrityError) 61 | end 62 | 63 | it "should compare black list in a case insensitive manner when lowercase extension provided" do 64 | allow(@uploader).to receive(:extension_black_list).and_return(%w(JPG GIF PNG)) 65 | expect(running { 66 | @uploader.cache!(File.open(file_path('test.jpg'))) 67 | }).to raise_error(CarrierWave::IntegrityError) 68 | end 69 | 70 | it "should accept and check regular expressions" do 71 | allow(@uploader).to receive(:extension_black_list).and_return([/jpe?g/, 'gif', 'png']) 72 | expect(running { 73 | @uploader.cache!(File.open(file_path('test.jpeg'))) 74 | }).to raise_error(CarrierWave::IntegrityError) 75 | end 76 | end 77 | 78 | end 79 | -------------------------------------------------------------------------------- /features/versions_basics.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and versions 2 | In order to be awesome 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader class has a version named 'thumb' 9 | And an instance of that class 10 | 11 | Scenario: store a file 12 | When I store the file 'fixtures/bork.txt' 13 | Then there should be a file at 'public/uploads/bork.txt' 14 | Then there should be a file at 'public/uploads/thumb_bork.txt' 15 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 16 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 17 | And the uploader should have the url '/uploads/bork.txt' 18 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 19 | 20 | Scenario: cache a file and then store it 21 | When I cache the file 'fixtures/bork.txt' 22 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 23 | Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 24 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 25 | And there should not be a file at 'public/uploads/bork.txt' 26 | And there should not be a file at 'public/uploads/thumb_bork.txt' 27 | When I store the file 28 | Then there should be a file at 'public/uploads/bork.txt' 29 | And there should be a file at 'public/uploads/thumb_bork.txt' 30 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 31 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 32 | And the uploader should have the url '/uploads/bork.txt' 33 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 34 | 35 | Scenario: retrieving a file from cache then storing 36 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 37 | Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' 38 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 39 | And I store the file 40 | Then there should be a file at 'public/uploads/bork.txt' 41 | Then there should be a file at 'public/uploads/thumb_bork.txt' 42 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 43 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' 44 | 45 | Scenario: retrieving a file from store 46 | Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' 47 | Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' 48 | When I retrieve the file 'bork.txt' from the store 49 | Then the uploader should have the url '/uploads/bork.txt' 50 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 51 | -------------------------------------------------------------------------------- /lib/carrierwave/orm/activerecord.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'active_record' 4 | require 'carrierwave/validations/active_model' 5 | 6 | module CarrierWave 7 | module ActiveRecord 8 | 9 | include CarrierWave::Mount 10 | 11 | ## 12 | # See +CarrierWave::Mount#mount_uploader+ for documentation 13 | # 14 | def mount_uploader(column, uploader=nil, options={}, &block) 15 | super 16 | 17 | class_eval <<-RUBY, __FILE__, __LINE__+1 18 | def remote_#{column}_url=(url) 19 | column = _mounter(:#{column}).serialization_column 20 | send(:"\#{column}_will_change!") 21 | super 22 | end 23 | RUBY 24 | end 25 | 26 | ## 27 | # See +CarrierWave::Mount#mount_uploaders+ for documentation 28 | # 29 | def mount_uploaders(column, uploader=nil, options={}, &block) 30 | super 31 | 32 | class_eval <<-RUBY, __FILE__, __LINE__+1 33 | def remote_#{column}_urls=(url) 34 | column = _mounter(:#{column}).serialization_column 35 | send(:"\#{column}_will_change!") 36 | super 37 | end 38 | RUBY 39 | end 40 | 41 | private 42 | 43 | def mount_base(column, uploader=nil, options={}, &block) 44 | super 45 | 46 | alias_method :read_uploader, :read_attribute 47 | alias_method :write_uploader, :write_attribute 48 | public :read_uploader 49 | public :write_uploader 50 | 51 | include CarrierWave::Validations::ActiveModel 52 | 53 | validates_integrity_of column if uploader_option(column.to_sym, :validate_integrity) 54 | validates_processing_of column if uploader_option(column.to_sym, :validate_processing) 55 | validates_download_of column if uploader_option(column.to_sym, :validate_download) 56 | 57 | after_save :"store_#{column}!" 58 | before_save :"write_#{column}_identifier" 59 | after_commit :"remove_#{column}!", :on => :destroy 60 | after_commit :"mark_remove_#{column}_false", :on => :update 61 | 62 | after_save :"store_previous_changes_for_#{column}" 63 | after_commit :"remove_previously_stored_#{column}", :on => :update 64 | 65 | class_eval <<-RUBY, __FILE__, __LINE__+1 66 | def #{column}=(new_file) 67 | column = _mounter(:#{column}).serialization_column 68 | if !(new_file.blank? && send(:#{column}).blank?) 69 | send(:"\#{column}_will_change!") 70 | end 71 | 72 | super 73 | end 74 | 75 | def remove_#{column}=(value) 76 | send(:"#{column}_will_change!") 77 | super 78 | end 79 | 80 | def remove_#{column}! 81 | super 82 | self.remove_#{column} = true 83 | write_#{column}_identifier 84 | end 85 | 86 | # Reset cached mounter on record reload 87 | def reload(*) 88 | @_mounters = nil 89 | super 90 | end 91 | 92 | # Reset cached mounter on record dup 93 | def initialize_dup(other) 94 | @_mounters = nil 95 | super 96 | end 97 | RUBY 98 | end 99 | 100 | end # ActiveRecord 101 | end # CarrierWave 102 | 103 | ActiveRecord::Base.extend CarrierWave::ActiveRecord 104 | -------------------------------------------------------------------------------- /features/step_definitions/general_steps.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Given /^an uploader class that uses the '(.*?)' storage$/ do |kind| 4 | @klass = Class.new(CarrierWave::Uploader::Base) 5 | @klass.storage = kind.to_sym 6 | end 7 | 8 | Given /^an instance of that class$/ do 9 | @uploader = @klass.new 10 | end 11 | 12 | Given /^a processor method named :upcase$/ do 13 | @klass.class_eval do 14 | define_method(:upcase) do 15 | content = File.read(current_path) 16 | File.open(current_path, 'w') { |f| f.write content.upcase } 17 | end 18 | end 19 | end 20 | 21 | Then /^the contents of the file should be '(.*?)'$/ do |contents| 22 | @uploader.read.chomp.should == contents 23 | end 24 | 25 | Given /^that the uploader reverses the filename$/ do 26 | @klass.class_eval do 27 | def filename 28 | super.reverse unless super.blank? 29 | end 30 | end 31 | end 32 | 33 | Given /^that the uploader has the filename overridden to '(.*?)'$/ do |filename| 34 | @klass.class_eval do 35 | define_method(:filename) do 36 | filename 37 | end 38 | end 39 | end 40 | 41 | Given /^that the uploader has the store_dir overridden to '(.*?)'$/ do |store_dir| 42 | @klass.class_eval do 43 | define_method(:store_dir) do 44 | file_path(store_dir) 45 | end 46 | end 47 | end 48 | 49 | Given /^that the version '(.*?)' has the store_dir overridden to '(.*?)'$/ do |version, store_dir| 50 | @klass.versions[version.to_sym].class_eval do 51 | define_method(:store_dir) do 52 | file_path(store_dir) 53 | end 54 | end 55 | end 56 | 57 | Given /^that the uploader class has a version named '([^\']+)'$/ do |name| 58 | @klass.version(name) 59 | end 60 | 61 | Given /^that the uploader class has a version named '([^\']+)' which process '([a-zA-Z0-9\_\?!]*)'$/ do |name, processor_name| 62 | @klass.version(name) do 63 | process processor_name.to_sym 64 | end 65 | end 66 | 67 | Given /^that the uploader class has a version named '([^\']+)' which is based on version '(.*?)'$/ do |name, based_version_name| 68 | @klass.version(name, {:from_version => based_version_name.to_sym}) 69 | end 70 | 71 | Given /^yo dawg, I put a version called '(.*?)' in your version called '(.*?)'$/ do |v2, v1| 72 | @klass.version(v1) do 73 | version(v2) 74 | end 75 | end 76 | 77 | Given /^the class has a method called 'reverse' that reverses the contents of a file$/ do 78 | @klass.class_eval do 79 | def reverse 80 | text = File.read(current_path) 81 | File.open(current_path, 'w') { |f| f.write(text.reverse) } 82 | end 83 | end 84 | end 85 | 86 | Given /^the class will process '([a-zA-Z0-9\_\?!]*)'$/ do |name| 87 | @klass.process name.to_sym 88 | end 89 | 90 | Then /^the uploader should have '(.*?)' as its current path$/ do |path| 91 | @uploader.current_path.should == file_path(path) 92 | end 93 | 94 | Then /^the uploader should have the url '(.*?)'$/ do |url| 95 | @uploader.url.should == url 96 | end 97 | 98 | Then /^the uploader's version '(.*?)' should have the url '(.*?)'$/ do |version, url| 99 | @uploader.versions[version.to_sym].url.should == url 100 | end 101 | 102 | Then /^the uploader's nested version '(.*?)' nested in '(.*?)' should have the url '(.*?)'$/ do |v2, v1, url| 103 | @uploader.versions[v1.to_sym].versions[v2.to_sym].url.should == url 104 | end 105 | -------------------------------------------------------------------------------- /features/versions_overridden_filename.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with file storage and overriden filename 2 | In order to customize the filaname of uploaded files 3 | As a developer using CarrierWave 4 | I want to upload files to the filesystem with an overriden filename and different verions 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader class has a version named 'thumb' 9 | And that the uploader has the filename overridden to 'grark.png' 10 | And an instance of that class 11 | 12 | Scenario: store a file 13 | When I store the file 'fixtures/bork.txt' 14 | Then there should be a file at 'public/uploads/grark.png' 15 | Then there should be a file at 'public/uploads/thumb_grark.png' 16 | And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' 17 | And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/bork.txt' 18 | And the uploader should have the url '/uploads/grark.png' 19 | And the uploader's version 'thumb' should have the url '/uploads/thumb_grark.png' 20 | 21 | Scenario: cache a file and then store it 22 | When I cache the file 'fixtures/bork.txt' 23 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 24 | Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 25 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 26 | And there should not be a file at 'public/uploads/grark.png' 27 | And there should not be a file at 'public/uploads/thumb_grark.png' 28 | When I store the file 29 | Then there should be a file at 'public/uploads/grark.png' 30 | And there should be a file at 'public/uploads/thumb_grark.png' 31 | And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' 32 | And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/bork.txt' 33 | And the uploader should have the url '/uploads/grark.png' 34 | And the uploader's version 'thumb' should have the url '/uploads/thumb_grark.png' 35 | 36 | Scenario: retrieving a file from cache then storing 37 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 38 | Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' 39 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 40 | And I store the file 41 | Then there should be a file at 'public/uploads/grark.png' 42 | Then there should be a file at 'public/uploads/thumb_grark.png' 43 | And the file at 'public/uploads/grark.png' should be identical to the file at 'fixtures/bork.txt' 44 | And the file at 'public/uploads/thumb_grark.png' should be identical to the file at 'fixtures/monkey.txt' 45 | 46 | Scenario: retrieving a file from store 47 | Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' 48 | Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' 49 | When I retrieve the file 'bork.txt' from the store 50 | Then the uploader should have the url '/uploads/bork.txt' 51 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 52 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | 6 | require 'pry' 7 | require 'tempfile' 8 | require 'time' 9 | require 'logger' 10 | 11 | require 'carrierwave' 12 | require 'timecop' 13 | require 'open-uri' 14 | require 'sham_rack' 15 | require 'mini_magick' 16 | 17 | I18n.enforce_available_locales = false 18 | 19 | CARRIERWAVE_DIRECTORY = "carrierwave#{Time.now.to_i}" unless defined?(CARRIERWAVE_DIRECTORY) 20 | 21 | alias :running :lambda 22 | 23 | def file_path( *paths ) 24 | File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', *paths)) 25 | end 26 | 27 | def public_path( *paths ) 28 | File.expand_path(File.join(File.dirname(__FILE__), 'public', *paths)) 29 | end 30 | 31 | def tmp_path( *paths ) 32 | File.expand_path(File.join(File.dirname(__FILE__), 'tmp', *paths)) 33 | end 34 | 35 | CarrierWave.root = public_path 36 | I18n.load_path << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "carrierwave", "locale", 'en.yml')) 37 | 38 | module CarrierWave 39 | module Test 40 | module MockStorage 41 | def mock_storage(kind) 42 | storage = double("storage for #{kind} uploader") 43 | allow(storage).to receive(:setup!) 44 | storage 45 | end 46 | end 47 | 48 | module MockFiles 49 | def stub_tempfile(filename, mime_type=nil, fake_name=nil) 50 | raise "#{path} file does not exist" unless File.exist?(file_path(filename)) 51 | 52 | tempfile = Tempfile.new(filename) 53 | FileUtils.copy_file(file_path(filename), tempfile.path) 54 | allow(tempfile).to receive_messages(:original_filename => fake_name || filename, 55 | :content_type => mime_type) 56 | tempfile 57 | end 58 | 59 | alias_method :stub_merb_tempfile, :stub_tempfile 60 | 61 | def stub_stringio(filename, mime_type=nil, fake_name=nil) 62 | file = IO.read( file_path( filename ) ) if filename 63 | stringio = StringIO.new(file) 64 | allow(stringio).to receive_messages(:local_path => "", 65 | :original_filename => filename || fake_name, 66 | :content_type => mime_type) 67 | stringio 68 | end 69 | 70 | def stub_file(filename, mime_type=nil, fake_name=nil) 71 | File.open(file_path(filename)) 72 | end 73 | end 74 | 75 | module I18nHelpers 76 | def change_locale_and_store_translations(locale, translations, &block) 77 | current_locale = I18n.locale 78 | begin 79 | I18n.backend.store_translations locale, translations 80 | I18n.locale = locale 81 | yield 82 | ensure 83 | I18n.reload! 84 | I18n.locale = current_locale 85 | end 86 | end 87 | end 88 | 89 | module ManipulationHelpers 90 | def color_of_pixel(path, x, y) 91 | image = ::MiniMagick::Image.open(path) 92 | color = image.run_command("convert", "#{image.path}[1x1+#{x}+#{y}]", "-depth", "8", "txt:").split("\n")[1] 93 | end 94 | end 95 | end 96 | end 97 | 98 | RSpec.configure do |config| 99 | config.include CarrierWave::Test::Matchers 100 | config.include CarrierWave::Test::MockFiles 101 | config.include CarrierWave::Test::MockStorage 102 | config.include CarrierWave::Test::I18nHelpers 103 | config.include CarrierWave::Test::ManipulationHelpers 104 | if RUBY_ENGINE == 'jruby' 105 | config.filter_run_excluding :rmagick => true 106 | config.filter_run_excluding :filemagic => true 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/carrierwave/storage/file.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Storage 5 | 6 | ## 7 | # File storage stores file to the Filesystem (surprising, no?). There's really not much 8 | # to it, it uses the store_dir defined on the uploader as the storage location. That's 9 | # pretty much it. 10 | # 11 | class File < Abstract 12 | 13 | ## 14 | # Move the file to the uploader's store path. 15 | # 16 | # By default, store!() uses copy_to(), which operates by copying the file 17 | # from the cache to the store, then deleting the file from the cache. 18 | # If move_to_store() is overriden to return true, then store!() uses move_to(), 19 | # which simply moves the file from cache to store. Useful for large files. 20 | # 21 | # === Parameters 22 | # 23 | # [file (CarrierWave::SanitizedFile)] the file to store 24 | # 25 | # === Returns 26 | # 27 | # [CarrierWave::SanitizedFile] a sanitized file 28 | # 29 | def store!(file) 30 | path = ::File.expand_path(uploader.store_path, uploader.root) 31 | if uploader.move_to_store 32 | file.move_to(path, uploader.permissions, uploader.directory_permissions) 33 | else 34 | file.copy_to(path, uploader.permissions, uploader.directory_permissions) 35 | end 36 | end 37 | 38 | ## 39 | # Retrieve the file from its store path 40 | # 41 | # === Parameters 42 | # 43 | # [identifier (String)] the filename of the file 44 | # 45 | # === Returns 46 | # 47 | # [CarrierWave::SanitizedFile] a sanitized file 48 | # 49 | def retrieve!(identifier) 50 | path = ::File.expand_path(uploader.store_path(identifier), uploader.root) 51 | CarrierWave::SanitizedFile.new(path) 52 | end 53 | 54 | ## 55 | # Stores given file to cache directory. 56 | # 57 | # === Parameters 58 | # 59 | # [new_file (File, IOString, Tempfile)] any kind of file object 60 | # 61 | # === Returns 62 | # 63 | # [CarrierWave::SanitizedFile] a sanitized file 64 | # 65 | def cache!(new_file) 66 | new_file.move_to(::File.expand_path(uploader.cache_path, uploader.root), uploader.permissions, uploader.directory_permissions, true) 67 | end 68 | 69 | ## 70 | # Retrieves the file with the given cache_name from the cache. 71 | # 72 | # === Parameters 73 | # 74 | # [cache_name (String)] uniquely identifies a cache file 75 | # 76 | # === Raises 77 | # 78 | # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted. 79 | # 80 | def retrieve_from_cache!(identifier) 81 | CarrierWave::SanitizedFile.new(::File.expand_path(uploader.cache_path(identifier), uploader.root)) 82 | end 83 | 84 | ## 85 | # Deletes a cache dir 86 | # 87 | def delete_dir!(path) 88 | if path 89 | begin 90 | Dir.rmdir(::File.expand_path(path, uploader.root)) 91 | rescue Errno::ENOENT 92 | # Ignore: path does not exist 93 | rescue Errno::ENOTDIR 94 | # Ignore: path is not a dir 95 | rescue Errno::ENOTEMPTY, Errno::EEXIST 96 | # Ignore: dir is not empty 97 | end 98 | end 99 | end 100 | 101 | def clean_cache!(seconds) 102 | Dir.glob(::File.expand_path(::File.join(uploader.cache_dir, '*'), CarrierWave.root)).each do |dir| 103 | time = dir.scan(/(\d+)-\d+-\d+$/).first.map(&:to_i) 104 | time = Time.at(*time) 105 | if time < (Time.now.utc - seconds) 106 | FileUtils.rm_rf(dir) 107 | end 108 | end 109 | end 110 | end # File 111 | end # Storage 112 | end # CarrierWave 113 | -------------------------------------------------------------------------------- /lib/carrierwave/compatibility/paperclip.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | module Compatibility 5 | 6 | ## 7 | # Mix this module into an Uploader to make it mimic Paperclip's storage paths 8 | # This will make your Uploader use the same default storage path as paperclip 9 | # does. If you need to override it, you can override the +paperclip_path+ method 10 | # and provide a Paperclip style path: 11 | # 12 | # class MyUploader < CarrierWave::Uploader::Base 13 | # include CarrierWave::Compatibility::Paperclip 14 | # 15 | # def paperclip_path 16 | # ":rails_root/public/uploads/:id/:attachment/:style_:basename.:extension" 17 | # end 18 | # end 19 | # 20 | # --- 21 | # 22 | # This file contains code taken from Paperclip 23 | # 24 | # LICENSE 25 | # 26 | # The MIT License 27 | # 28 | # Copyright (c) 2008 Jon Yurek and thoughtbot, inc. 29 | # 30 | # Permission is hereby granted, free of charge, to any person obtaining a copy 31 | # of this software and associated documentation files (the "Software"), to deal 32 | # in the Software without restriction, including without limitation the rights 33 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | # copies of the Software, and to permit persons to whom the Software is 35 | # furnished to do so, subject to the following conditions: 36 | # 37 | # The above copyright notice and this permission notice shall be included in 38 | # all copies or substantial portions of the Software. 39 | # 40 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 46 | # THE SOFTWARE. 47 | # 48 | module Paperclip 49 | extend ActiveSupport::Concern 50 | 51 | DEFAULT_MAPPINGS = { 52 | :rails_root => lambda{|u, f| Rails.root.to_s }, 53 | :rails_env => lambda{|u, f| Rails.env }, 54 | :id_partition => lambda{|u, f| ("%09d" % u.model.id).scan(/\d{3}/).join("/")}, 55 | :id => lambda{|u, f| u.model.id }, 56 | :attachment => lambda{|u, f| u.mounted_as.to_s.downcase.pluralize }, 57 | :style => lambda{|u, f| u.paperclip_style }, 58 | :basename => lambda{|u, f| u.filename.gsub(/#{File.extname(u.filename)}$/, "") }, 59 | :extension => lambda{|u, d| File.extname(u.filename).gsub(/^\.+/, "")}, 60 | :class => lambda{|u, f| u.model.class.name.underscore.pluralize} 61 | } 62 | 63 | included do 64 | attr_accessor :filename 65 | class_attribute :mappings 66 | self.mappings ||= DEFAULT_MAPPINGS.dup 67 | end 68 | 69 | def store_path(for_file=filename) 70 | path = paperclip_path 71 | self.filename = for_file 72 | path ||= File.join(*[store_dir, paperclip_style.to_s, for_file].compact) 73 | interpolate_paperclip_path(path) 74 | end 75 | 76 | def store_dir 77 | ":rails_root/public/system/:attachment/:id" 78 | end 79 | 80 | def paperclip_default_style 81 | :original 82 | end 83 | 84 | def paperclip_path 85 | end 86 | 87 | def paperclip_style 88 | version_name || paperclip_default_style 89 | end 90 | 91 | module ClassMethods 92 | def interpolate(sym, &block) 93 | mappings[sym] = block 94 | end 95 | end 96 | 97 | private 98 | def interpolate_paperclip_path(path) 99 | mappings.each_pair.inject(path) do |agg, pair| 100 | agg.gsub(":#{pair[0]}") { pair[1].call(self, self.paperclip_style).to_s } 101 | end 102 | end 103 | end # Paperclip 104 | end # Compatibility 105 | end # CarrierWave 106 | -------------------------------------------------------------------------------- /spec/uploader/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | 6 | describe CarrierWave do 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | end 10 | 11 | describe '.configure' do 12 | it "should proxy to Uploader configuration" do 13 | CarrierWave::Uploader::Base.add_config :test_config 14 | CarrierWave.configure do |config| 15 | config.test_config = "foo" 16 | end 17 | expect(CarrierWave::Uploader::Base.test_config).to eq('foo') 18 | end 19 | end 20 | end 21 | 22 | describe CarrierWave::Uploader::Base do 23 | before do 24 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 25 | end 26 | 27 | describe '.configure' do 28 | it "should set a configuration parameter" do 29 | @uploader_class.add_config :foo_bar 30 | @uploader_class.configure do |config| 31 | config.foo_bar = "monkey" 32 | end 33 | expect(@uploader_class.foo_bar).to eq('monkey') 34 | end 35 | end 36 | 37 | describe ".storage" do 38 | it "should set the storage if an argument is given" do 39 | storage = double('some kind of storage') 40 | @uploader_class.storage storage 41 | expect(@uploader_class.storage).to eq(storage) 42 | end 43 | 44 | it "should default to file" do 45 | expect(@uploader_class.storage).to eq(CarrierWave::Storage::File) 46 | end 47 | 48 | it "should set the storage from the configured shortcuts if a symbol is given" do 49 | @uploader_class.storage :file 50 | expect(@uploader_class.storage).to eq(CarrierWave::Storage::File) 51 | end 52 | 53 | it "should remember the storage when inherited" do 54 | @uploader_class.storage :fog 55 | subclass = Class.new(@uploader_class) 56 | expect(subclass.storage).to eq(CarrierWave::Storage::Fog) 57 | end 58 | 59 | it "should be changeable when inherited" do 60 | @uploader_class.storage :fog 61 | subclass = Class.new(@uploader_class) 62 | expect(subclass.storage).to eq(CarrierWave::Storage::Fog) 63 | subclass.storage :file 64 | expect(subclass.storage).to eq(CarrierWave::Storage::File) 65 | end 66 | end 67 | 68 | 69 | describe '.add_config' do 70 | it "should add a class level accessor" do 71 | @uploader_class.add_config :foo_bar 72 | @uploader_class.foo_bar = 'foo' 73 | expect(@uploader_class.foo_bar).to eq('foo') 74 | end 75 | 76 | ['foo', :foo, 45, ['foo', :bar]].each do |val| 77 | it "should be inheritable for a #{val.class}" do 78 | @uploader_class.add_config :foo_bar 79 | @child_class = Class.new(@uploader_class) 80 | 81 | @uploader_class.foo_bar = val 82 | expect(@uploader_class.foo_bar).to eq(val) 83 | expect(@child_class.foo_bar).to eq(val) 84 | 85 | @child_class.foo_bar = "bar" 86 | expect(@child_class.foo_bar).to eq("bar") 87 | 88 | expect(@uploader_class.foo_bar).to eq(val) 89 | end 90 | end 91 | 92 | 93 | it "should add an instance level accessor" do 94 | @uploader_class.add_config :foo_bar 95 | @uploader_class.foo_bar = 'foo' 96 | expect(@uploader_class.new.foo_bar).to eq('foo') 97 | end 98 | 99 | it "should add a convenient in-class setter" do 100 | @uploader_class.add_config :foo_bar 101 | @uploader_class.foo_bar "monkey" 102 | expect(@uploader_class.foo_bar).to eq("monkey") 103 | end 104 | 105 | describe "assigning a proc to a config attribute" do 106 | before(:each) do 107 | @uploader_class.add_config :hoobatz 108 | @uploader_class.hoobatz = this_proc 109 | end 110 | 111 | context "when the proc accepts no arguments" do 112 | let(:this_proc) { proc { "a return value" } } 113 | 114 | it "calls the proc without arguments" do 115 | expect(@uploader_class.new.hoobatz).to eq("a return value") 116 | end 117 | end 118 | 119 | context "when the proc accepts one argument" do 120 | let(:this_proc) { proc { |arg1| expect(arg1).to be_an_instance_of(@uploader_class) } } 121 | 122 | it "calls the proc with an instance of the uploader" do 123 | @uploader_class.new.hoobatz 124 | end 125 | end 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/carrierwave/mounter.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | 3 | # this is an internal class, used by CarrierWave::Mount so that 4 | # we don't pollute the model with a lot of methods. 5 | class Mounter #:nodoc: 6 | attr_reader :column, :record, :remote_urls, :integrity_error, :processing_error, :download_error 7 | attr_accessor :remove 8 | 9 | def initialize(record, column, options={}) 10 | @record = record 11 | @column = column 12 | @options = record.class.uploader_options[column] 13 | end 14 | 15 | def uploader_class 16 | record.class.uploaders[column] 17 | end 18 | 19 | def blank_uploader 20 | uploader_class.new(record, column) 21 | end 22 | 23 | def identifiers 24 | uploaders.map(&:identifier) 25 | end 26 | 27 | def read_identifiers 28 | [record.read_uploader(serialization_column)].flatten.reject(&:blank?) 29 | end 30 | 31 | def uploaders 32 | @uploaders ||= read_identifiers.map do |identifier| 33 | uploader = blank_uploader 34 | uploader.retrieve_from_store!(identifier) if identifier.present? 35 | uploader 36 | end 37 | end 38 | 39 | def cache(new_files) 40 | return if not new_files or new_files == "" 41 | @uploaders = new_files.map do |new_file| 42 | uploader = blank_uploader 43 | uploader.cache!(new_file) 44 | uploader 45 | end 46 | 47 | @integrity_error = nil 48 | @processing_error = nil 49 | rescue CarrierWave::IntegrityError => e 50 | @integrity_error = e 51 | raise e unless option(:ignore_integrity_errors) 52 | rescue CarrierWave::ProcessingError => e 53 | @processing_error = e 54 | raise e unless option(:ignore_processing_errors) 55 | end 56 | 57 | def cache_names 58 | uploaders.map(&:cache_name).compact 59 | end 60 | 61 | def cache_names=(cache_names) 62 | return if not cache_names or cache_names == "" or uploaders.any?(&:cached?) 63 | @uploaders = cache_names.map do |cache_name| 64 | uploader = blank_uploader 65 | uploader.retrieve_from_cache!(cache_name) 66 | uploader 67 | end 68 | rescue CarrierWave::InvalidParameter 69 | end 70 | 71 | def remote_urls=(urls) 72 | return if not urls or urls == "" or urls.all?(&:blank?) 73 | 74 | @remote_urls = urls 75 | @download_error = nil 76 | @integrity_error = nil 77 | 78 | @uploaders = urls.map do |url| 79 | uploader = blank_uploader 80 | uploader.download!(url) 81 | uploader 82 | end 83 | 84 | rescue CarrierWave::DownloadError => e 85 | @download_error = e 86 | raise e unless option(:ignore_download_errors) 87 | rescue CarrierWave::ProcessingError => e 88 | @processing_error = e 89 | raise e unless option(:ignore_processing_errors) 90 | rescue CarrierWave::IntegrityError => e 91 | @integrity_error = e 92 | raise e unless option(:ignore_integrity_errors) 93 | end 94 | 95 | def store! 96 | if remove? 97 | remove! 98 | else 99 | uploaders.reject(&:blank?).each(&:store!) 100 | end 101 | end 102 | 103 | def urls(*args) 104 | uploaders.map { |u| u.url(*args) } 105 | end 106 | 107 | def blank? 108 | uploaders.empty? 109 | end 110 | 111 | def remove? 112 | remove.present? && remove !~ /\A0|false$\z/ 113 | end 114 | 115 | def remove! 116 | uploaders.reject(&:blank?).each(&:remove!) 117 | @uploaders = [] 118 | end 119 | 120 | def serialization_column 121 | option(:mount_on) || column 122 | end 123 | 124 | def remove_previous(before=nil, after=nil) 125 | if before 126 | before = before.reject(&:blank?).map do |value| 127 | if value.is_a?(String) 128 | uploader = blank_uploader 129 | uploader.retrieve_from_store!(before) 130 | uploader 131 | else 132 | value 133 | end 134 | end 135 | after_paths = after.reject(&:blank?).map { |value| value.respond_to?(:path) ? value.path : value } 136 | before.each do |uploader| 137 | if uploader.remove_previously_stored_files_after_update and not after_paths.include?(uploader.path) 138 | uploader.remove! 139 | end 140 | end 141 | end 142 | end 143 | 144 | attr_accessor :uploader_options 145 | 146 | private 147 | 148 | def option(name) 149 | self.uploader_options ||= {} 150 | self.uploader_options[name] ||= record.class.uploader_option(column, name) 151 | end 152 | 153 | end # Mounter 154 | end # CarrierWave 155 | -------------------------------------------------------------------------------- /spec/compatibility/paperclip_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | require 'carrierwave/orm/activerecord' 6 | 7 | module Rails; end unless defined?(Rails) 8 | 9 | describe CarrierWave::Compatibility::Paperclip do 10 | 11 | before do 12 | allow(Rails).to receive(:root).and_return('/rails/root') 13 | allow(Rails).to receive(:env).and_return('test') 14 | @uploader_class = Class.new(CarrierWave::Uploader::Base) do 15 | include CarrierWave::Compatibility::Paperclip 16 | 17 | version :thumb 18 | version :list 19 | 20 | end 21 | 22 | @model = double('a model') 23 | allow(@model).to receive(:id).and_return(23) 24 | allow(@model).to receive(:ook).and_return('eek') 25 | allow(@model).to receive(:money).and_return('monkey.png') 26 | 27 | @uploader = @uploader_class.new(@model, :monkey) 28 | end 29 | 30 | after do 31 | FileUtils.rm_rf(public_path) 32 | end 33 | 34 | describe '#store_path' do 35 | it "should mimics paperclip default" do 36 | expect(@uploader.store_path("monkey.png")).to eq("/rails/root/public/system/monkeys/23/original/monkey.png") 37 | end 38 | 39 | it "should interpolate the root path" do 40 | allow(@uploader).to receive(:paperclip_path).and_return(":rails_root/foo/bar") 41 | expect(@uploader.store_path("monkey.png")).to eq(Rails.root + "/foo/bar") 42 | end 43 | 44 | it "should interpolate the attachment" do 45 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:attachment/bar") 46 | expect(@uploader.store_path("monkey.png")).to eq("/foo/monkeys/bar") 47 | end 48 | 49 | it "should interpolate the id" do 50 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:id/bar") 51 | expect(@uploader.store_path("monkey.png")).to eq("/foo/23/bar") 52 | end 53 | 54 | it "should interpolate the id partition" do 55 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:id_partition/bar") 56 | expect(@uploader.store_path("monkey.png")).to eq("/foo/000/000/023/bar") 57 | end 58 | 59 | it "should interpolate the basename" do 60 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:basename/bar") 61 | expect(@uploader.store_path("monkey.png")).to eq("/foo/monkey/bar") 62 | end 63 | 64 | it "should interpolate the extension" do 65 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:extension/bar") 66 | expect(@uploader.store_path("monkey.png")).to eq("/foo/png/bar") 67 | end 68 | 69 | end 70 | 71 | describe '.interpolate' do 72 | before do 73 | @uploader_class.interpolate :ook do |custom, style| 74 | custom.model.ook 75 | end 76 | 77 | 78 | @uploader_class.interpolate :aak do |model, style| 79 | style 80 | end 81 | end 82 | 83 | it 'should allow you to add custom interpolations' do 84 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:id/:ook") 85 | expect(@uploader.store_path("monkey.png")).to eq('/foo/23/eek') 86 | end 87 | 88 | it 'mimics paperclips arguments' do 89 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:aak") 90 | expect(@uploader.store_path("monkey.png")).to eq('/foo/original') 91 | end 92 | 93 | context 'when multiple uploaders include the compatibility module' do 94 | before do 95 | @uploader_class_other = Class.new(CarrierWave::Uploader::Base) do 96 | include CarrierWave::Compatibility::Paperclip 97 | 98 | version :thumb 99 | version :list 100 | end 101 | 102 | @uploader = @uploader_class_other.new(@model, :monkey) 103 | end 104 | 105 | it 'should not share custom interpolations' do 106 | allow(@uploader).to receive(:paperclip_path).and_return("/foo/:id/:ook") 107 | expect(@uploader.store_path('monkey.jpg')).to eq('/foo/23/:ook') 108 | end 109 | 110 | end 111 | 112 | context 'when there are multiple versions' do 113 | before do 114 | @complex_uploader_class = Class.new(CarrierWave::Uploader::Base) do 115 | include CarrierWave::Compatibility::Paperclip 116 | 117 | interpolate :ook do |model, style| 118 | 'eek' 119 | end 120 | 121 | version :thumb 122 | version :list 123 | 124 | def paperclip_path 125 | "#{public_path}/foo/:ook/:id/:style" 126 | end 127 | end 128 | 129 | @uploader = @complex_uploader_class.new(@model, :monkey) 130 | end 131 | 132 | it 'should interpolate for all versions correctly' do 133 | @file = File.open(file_path('test.jpg')) 134 | @uploader.store!(@file) 135 | expect(@uploader.thumb.path).to eq("#{public_path}/foo/eek/23/thumb") 136 | expect(@uploader.list.path).to eq("#{public_path}/foo/eek/23/list") 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /features/versions_nested_versions.feature: -------------------------------------------------------------------------------- 1 | Feature: uploader with nested versions 2 | In order to optimize performance for processing 3 | As a developer using CarrierWave 4 | I want to set nested versions 5 | 6 | Background: 7 | Given an uploader class that uses the 'file' storage 8 | And that the uploader class has a version named 'thumb' 9 | And yo dawg, I put a version called 'mini' in your version called 'thumb' 10 | And yo dawg, I put a version called 'micro' in your version called 'thumb' 11 | And an instance of that class 12 | 13 | Scenario: store a file 14 | When I store the file 'fixtures/bork.txt' 15 | Then there should be a file at 'public/uploads/bork.txt' 16 | Then there should be a file at 'public/uploads/thumb_bork.txt' 17 | Then there should be a file at 'public/uploads/thumb_mini_bork.txt' 18 | Then there should be a file at 'public/uploads/thumb_micro_bork.txt' 19 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 20 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 21 | And the file at 'public/uploads/thumb_mini_bork.txt' should be identical to the file at 'fixtures/bork.txt' 22 | And the file at 'public/uploads/thumb_micro_bork.txt' should be identical to the file at 'fixtures/bork.txt' 23 | And the uploader should have the url '/uploads/bork.txt' 24 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 25 | And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' 26 | And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' 27 | 28 | Scenario: cache a file and then store it 29 | When I cache the file 'fixtures/bork.txt' 30 | Then there should be a file called 'bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 31 | Then there should be a file called 'thumb_bork.txt' somewhere in a subdirectory of 'public/uploads/tmp' 32 | And the file called 'bork.txt' in a subdirectory of 'public/uploads/tmp' should be identical to the file at 'fixtures/bork.txt' 33 | And there should not be a file at 'public/uploads/bork.txt' 34 | And there should not be a file at 'public/uploads/thumb_bork.txt' 35 | When I store the file 36 | Then there should be a file at 'public/uploads/bork.txt' 37 | And there should be a file at 'public/uploads/thumb_bork.txt' 38 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 39 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/bork.txt' 40 | And the uploader should have the url '/uploads/bork.txt' 41 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 42 | And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' 43 | And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' 44 | 45 | Scenario: retrieving a file from cache then storing 46 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/bork.txt' 47 | Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_bork.txt' 48 | Given the file 'fixtures/bork.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_mini_bork.txt' 49 | Given the file 'fixtures/monkey.txt' is cached file at 'public/uploads/tmp/1369894322-345-2255/thumb_micro_bork.txt' 50 | When I retrieve the cache name '1369894322-345-2255/bork.txt' from the cache 51 | And I store the file 52 | Then there should be a file at 'public/uploads/bork.txt' 53 | Then there should be a file at 'public/uploads/thumb_bork.txt' 54 | Then there should be a file at 'public/uploads/thumb_mini_bork.txt' 55 | Then there should be a file at 'public/uploads/thumb_micro_bork.txt' 56 | And the file at 'public/uploads/bork.txt' should be identical to the file at 'fixtures/bork.txt' 57 | And the file at 'public/uploads/thumb_bork.txt' should be identical to the file at 'fixtures/monkey.txt' 58 | And the file at 'public/uploads/thumb_mini_bork.txt' should be identical to the file at 'fixtures/bork.txt' 59 | And the file at 'public/uploads/thumb_micro_bork.txt' should be identical to the file at 'fixtures/monkey.txt' 60 | 61 | Scenario: retrieving a file from store 62 | Given the file 'fixtures/bork.txt' is stored at 'public/uploads/bork.txt' 63 | Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_bork.txt' 64 | Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_mini_bork.txt' 65 | Given the file 'fixtures/monkey.txt' is stored at 'public/uploads/thumb_micro_bork.txt' 66 | When I retrieve the file 'bork.txt' from the store 67 | Then the uploader should have the url '/uploads/bork.txt' 68 | And the uploader's version 'thumb' should have the url '/uploads/thumb_bork.txt' 69 | And the uploader's nested version 'mini' nested in 'thumb' should have the url '/uploads/thumb_mini_bork.txt' 70 | And the uploader's nested version 'micro' nested in 'thumb' should have the url '/uploads/thumb_micro_bork.txt' 71 | -------------------------------------------------------------------------------- /spec/uploader/processing_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::Uploader do 6 | 7 | before do 8 | @uploader_class = Class.new(CarrierWave::Uploader::Base) 9 | @uploader = @uploader_class.new 10 | end 11 | 12 | after do 13 | FileUtils.rm_rf(public_path) 14 | end 15 | 16 | describe '.process' do 17 | it "should add a single processor when a symbol is given" do 18 | @uploader_class.process :sepiatone 19 | expect(@uploader).to receive(:sepiatone) 20 | @uploader.process! 21 | end 22 | 23 | it "should add multiple processors when an array of symbols is given" do 24 | @uploader_class.process :sepiatone, :desaturate, :invert 25 | expect(@uploader).to receive(:sepiatone) 26 | expect(@uploader).to receive(:desaturate) 27 | expect(@uploader).to receive(:invert) 28 | @uploader.process! 29 | end 30 | 31 | it "should add a single processor with an argument when a hash is given" do 32 | @uploader_class.process :format => 'png' 33 | expect(@uploader).to receive(:format).with('png') 34 | @uploader.process! 35 | end 36 | 37 | it "should add a single processor with several argument when a hash is given" do 38 | @uploader_class.process :resize => [200, 300] 39 | expect(@uploader).to receive(:resize).with(200, 300) 40 | @uploader.process! 41 | end 42 | 43 | it "should add multiple processors when an hash with multiple keys is given" do 44 | @uploader_class.process :resize => [200, 300], :format => 'png' 45 | expect(@uploader).to receive(:resize).with(200, 300) 46 | expect(@uploader).to receive(:format).with('png') 47 | @uploader.process! 48 | end 49 | 50 | it "should call the processor if the condition method returns true" do 51 | @uploader_class.process :resize => [200, 300], :if => :true? 52 | @uploader_class.process :fancy, :if => :true? 53 | expect(@uploader).to receive(:true?).with("test.jpg").twice.and_return(true) 54 | expect(@uploader).to receive(:resize).with(200, 300) 55 | expect(@uploader).to receive(:fancy) 56 | @uploader.process!("test.jpg") 57 | end 58 | 59 | it "should not call the processor if the condition method returns false" do 60 | @uploader_class.process :resize => [200, 300], :if => :false? 61 | @uploader_class.process :fancy, :if => :false? 62 | expect(@uploader).to receive(:false?).with("test.jpg").twice.and_return(false) 63 | expect(@uploader).not_to receive(:resize) 64 | expect(@uploader).not_to receive(:fancy) 65 | @uploader.process!("test.jpg") 66 | end 67 | 68 | it "should call the processor if the condition block returns true" do 69 | @uploader_class.process :resize => [200, 300], :if => lambda{|record, args| record.true?(args[:file])} 70 | @uploader_class.process :fancy, :if => :true? 71 | expect(@uploader).to receive(:true?).with("test.jpg").twice.and_return(true) 72 | expect(@uploader).to receive(:resize).with(200, 300) 73 | expect(@uploader).to receive(:fancy) 74 | @uploader.process!("test.jpg") 75 | end 76 | 77 | it "should not call the processor if the condition block returns false" do 78 | @uploader_class.process :resize => [200, 300], :if => lambda{|record, args| record.false?(args[:file])} 79 | @uploader_class.process :fancy, :if => :false? 80 | expect(@uploader).to receive(:false?).with("test.jpg").twice.and_return(false) 81 | expect(@uploader).not_to receive(:resize) 82 | expect(@uploader).not_to receive(:fancy) 83 | @uploader.process!("test.jpg") 84 | end 85 | 86 | context "when using RMagick", :rmagick => true do 87 | before do 88 | def @uploader.cover 89 | manipulate! { |frame, index| frame if index.zero? } 90 | end 91 | 92 | @uploader_class.send :include, CarrierWave::RMagick 93 | end 94 | 95 | after do 96 | @uploader.instance_eval { undef cover } 97 | end 98 | 99 | context "with a multi-page PDF" do 100 | before do 101 | @uploader.cache! File.open(file_path("multi_page.pdf")) 102 | end 103 | 104 | it "should successfully process" do 105 | @uploader_class.process :convert => 'jpg' 106 | @uploader.process! 107 | end 108 | 109 | it "should support page specific transformations" do 110 | @uploader_class.process :cover 111 | @uploader.process! 112 | end 113 | end 114 | 115 | context "with a simple image" do 116 | before do 117 | @uploader.cache! File.open(file_path("portrait.jpg")) 118 | end 119 | 120 | it "should still allow page specific transformations" do 121 | @uploader_class.process :cover 122 | @uploader.process! 123 | end 124 | end 125 | end 126 | 127 | context "with 'enable_processing' set to false" do 128 | it "should not do any processing" do 129 | @uploader_class.enable_processing = false 130 | @uploader_class.process :sepiatone, :desaturate, :invert 131 | expect(@uploader).not_to receive(:sepiatone) 132 | expect(@uploader).not_to receive(:desaturate) 133 | expect(@uploader).not_to receive(:invert) 134 | @uploader.process! 135 | end 136 | end 137 | end 138 | 139 | describe '#cache!' do 140 | before do 141 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 142 | end 143 | 144 | it "should trigger a process!" do 145 | expect(@uploader).to receive(:process!) 146 | @uploader.cache!(File.open(file_path('test.jpg'))) 147 | end 148 | end 149 | 150 | describe '#recreate_versions!' do 151 | before do 152 | allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-2255') 153 | end 154 | 155 | it "should trigger a process!" do 156 | @uploader.store!(File.open(file_path('test.jpg'))) 157 | expect(@uploader).to receive(:process!) 158 | @uploader.recreate_versions! 159 | end 160 | end 161 | 162 | end 163 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/cache.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module CarrierWave 4 | 5 | class FormNotMultipart < UploadError 6 | def message 7 | "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." 8 | end 9 | end 10 | 11 | ## 12 | # Generates a unique cache id for use in the caching system 13 | # 14 | # === Returns 15 | # 16 | # [String] a cache id in the format TIMEINT-PID-RND 17 | # 18 | def self.generate_cache_id 19 | Time.now.utc.to_i.to_s + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999)) 20 | end 21 | 22 | module Uploader 23 | module Cache 24 | extend ActiveSupport::Concern 25 | 26 | include CarrierWave::Uploader::Callbacks 27 | include CarrierWave::Uploader::Configuration 28 | 29 | module ClassMethods 30 | 31 | ## 32 | # Removes cached files which are older than one day. You could call this method 33 | # from a rake task to clean out old cached files. 34 | # 35 | # You can call this method directly on the module like this: 36 | # 37 | # CarrierWave.clean_cached_files! 38 | # 39 | # === Note 40 | # 41 | # This only works as long as you haven't done anything funky with your cache_dir. 42 | # It's recommended that you keep cache files in one place only. 43 | # 44 | def clean_cached_files!(seconds=60*60*24) 45 | cache_storage.new(CarrierWave::Uploader::Base.new).clean_cache!(seconds) 46 | end 47 | end 48 | 49 | ## 50 | # Returns true if the uploader has been cached 51 | # 52 | # === Returns 53 | # 54 | # [Bool] whether the current file is cached 55 | # 56 | def cached? 57 | @cache_id 58 | end 59 | 60 | ## 61 | # Caches the remotely stored file 62 | # 63 | # This is useful when about to process images. Most processing solutions 64 | # require the file to be stored on the local filesystem. 65 | # 66 | def cache_stored_file! 67 | cache! 68 | end 69 | 70 | def sanitized_file 71 | _content = file.read 72 | if _content.is_a?(File) # could be if storage is Fog 73 | sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path)) 74 | else 75 | sanitized = SanitizedFile.new :tempfile => StringIO.new(_content), 76 | :filename => File.basename(path), :content_type => file.content_type 77 | end 78 | sanitized 79 | end 80 | 81 | ## 82 | # Returns a String which uniquely identifies the currently cached file for later retrieval 83 | # 84 | # === Returns 85 | # 86 | # [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt 87 | # 88 | def cache_name 89 | File.join(cache_id, full_original_filename) if cache_id and original_filename 90 | end 91 | 92 | ## 93 | # Caches the given file. Calls process! to trigger any process callbacks. 94 | # 95 | # By default, cache!() uses copy_to(), which operates by copying the file 96 | # to the cache, then deleting the original file. If move_to_cache() is 97 | # overriden to return true, then cache!() uses move_to(), which simply 98 | # moves the file to the cache. Useful for large files. 99 | # 100 | # === Parameters 101 | # 102 | # [new_file (File, IOString, Tempfile)] any kind of file object 103 | # 104 | # === Raises 105 | # 106 | # [CarrierWave::FormNotMultipart] if the assigned parameter is a string 107 | # 108 | def cache!(new_file = sanitized_file) 109 | new_file = CarrierWave::SanitizedFile.new(new_file) 110 | 111 | unless new_file.empty? 112 | raise CarrierWave::FormNotMultipart if new_file.is_path? && ensure_multipart_form 113 | 114 | self.cache_id = CarrierWave.generate_cache_id unless cache_id 115 | 116 | @filename = new_file.filename 117 | self.original_filename = new_file.filename 118 | 119 | begin 120 | # first, create a workfile on which we perform processings 121 | if move_to_cache 122 | @file = new_file.move_to(File.expand_path(workfile_path, root), permissions, directory_permissions) 123 | else 124 | @file = new_file.copy_to(File.expand_path(workfile_path, root), permissions, directory_permissions) 125 | end 126 | 127 | with_callbacks(:cache, @file) do 128 | @file = cache_storage.cache!(@file) 129 | end 130 | ensure 131 | FileUtils.rm_rf(workfile_path('')) 132 | end 133 | end 134 | end 135 | 136 | ## 137 | # Retrieves the file with the given cache_name from the cache. 138 | # 139 | # === Parameters 140 | # 141 | # [cache_name (String)] uniquely identifies a cache file 142 | # 143 | # === Raises 144 | # 145 | # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted. 146 | # 147 | def retrieve_from_cache!(cache_name) 148 | with_callbacks(:retrieve_from_cache, cache_name) do 149 | self.cache_id, self.original_filename = cache_name.to_s.split('/', 2) 150 | @filename = original_filename 151 | @file = cache_storage.retrieve_from_cache!(full_filename(original_filename)) 152 | end 153 | end 154 | 155 | ## 156 | # Calculates the path where the cache file should be stored. 157 | # 158 | # === Parameters 159 | # 160 | # [for_file (String)] name of the file 161 | # 162 | # === Returns 163 | # 164 | # [String] the cache path 165 | # 166 | def cache_path(for_file=full_filename(original_filename)) 167 | File.join(*[cache_dir, @cache_id, for_file].compact) 168 | end 169 | 170 | private 171 | 172 | def workfile_path(for_file=original_filename) 173 | File.join(CarrierWave.tmp_path, @cache_id, version_name.to_s, for_file) 174 | end 175 | 176 | attr_reader :cache_id, :original_filename 177 | 178 | # We can override the full_original_filename method in other modules 179 | alias_method :full_original_filename, :original_filename 180 | 181 | def cache_id=(cache_id) 182 | raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]+\-[\d]+\-[\d]{4}\z/ 183 | @cache_id = cache_id 184 | end 185 | 186 | def original_filename=(filename) 187 | raise CarrierWave::InvalidParameter, "invalid filename" if filename =~ CarrierWave::SanitizedFile.sanitize_regexp 188 | @original_filename = filename 189 | end 190 | 191 | def cache_storage 192 | @cache_storage ||= self.class.cache_storage.new(self) 193 | end 194 | end # Cache 195 | end # Uploader 196 | end # CarrierWave 197 | -------------------------------------------------------------------------------- /lib/carrierwave/uploader/configuration.rb: -------------------------------------------------------------------------------- 1 | module CarrierWave 2 | 3 | module Uploader 4 | module Configuration 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | class_attribute :_storage, :_cache_storage, :instance_writer => false 9 | 10 | add_config :root 11 | add_config :base_path 12 | add_config :asset_host 13 | add_config :permissions 14 | add_config :directory_permissions 15 | add_config :storage_engines 16 | add_config :store_dir 17 | add_config :cache_dir 18 | add_config :enable_processing 19 | add_config :ensure_multipart_form 20 | add_config :delete_tmp_file_after_storage 21 | add_config :move_to_cache 22 | add_config :move_to_store 23 | add_config :remove_previously_stored_files_after_update 24 | 25 | # fog 26 | add_config :fog_provider 27 | add_config :fog_attributes 28 | add_config :fog_credentials 29 | add_config :fog_directory 30 | add_config :fog_public 31 | add_config :fog_authenticated_url_expiration 32 | add_config :fog_use_ssl_for_aws 33 | 34 | # Mounting 35 | add_config :ignore_integrity_errors 36 | add_config :ignore_processing_errors 37 | add_config :ignore_download_errors 38 | add_config :validate_integrity 39 | add_config :validate_processing 40 | add_config :validate_download 41 | add_config :mount_on 42 | add_config :cache_only 43 | 44 | # set default values 45 | reset_config 46 | end 47 | 48 | module ClassMethods 49 | 50 | ## 51 | # Sets the storage engine to be used when storing files with this uploader. 52 | # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve! 53 | # method. See lib/carrierwave/storage/file.rb for an example. Storage engines should 54 | # be added to CarrierWave::Uploader::Base.storage_engines so they can be referred 55 | # to by a symbol, which should be more convenient 56 | # 57 | # If no argument is given, it will simply return the currently used storage engine. 58 | # 59 | # === Parameters 60 | # 61 | # [storage (Symbol, Class)] The storage engine to use for this uploader 62 | # 63 | # === Returns 64 | # 65 | # [Class] the storage engine to be used with this uploader 66 | # 67 | # === Examples 68 | # 69 | # storage :file 70 | # storage CarrierWave::Storage::File 71 | # storage MyCustomStorageEngine 72 | # 73 | def storage(storage = nil) 74 | if storage 75 | self._storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage 76 | end 77 | _storage 78 | end 79 | alias_method :storage=, :storage 80 | 81 | ## 82 | # Sets the cache storage engine to be used when storing cache files with this uploader. 83 | # Same as .storage except for required methods being #cache!(CarrierWave::SanitizedFile), 84 | # #retrieve_from_cache! and #delete_dir!. 85 | # 86 | # === Parameters 87 | # 88 | # [storage (Symbol, Class)] The cache storage engine to use for this uploader 89 | # 90 | # === Returns 91 | # 92 | # [Class] the cache storage engine to be used with this uploader 93 | # 94 | # === Examples 95 | # 96 | # cache_storage :file 97 | # cache_storage CarrierWave::Storage::File 98 | # cache_storage MyCustomStorageEngine 99 | # 100 | def cache_storage(storage = nil) 101 | if storage 102 | self._cache_storage = storage.is_a?(Symbol) ? eval(storage_engines[storage]) : storage 103 | end 104 | _cache_storage 105 | end 106 | alias_method :cache_storage=, :cache_storage 107 | 108 | def add_config(name) 109 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 110 | def self.eager_load_fog(fog_credentials) 111 | # see #1198. This will hopefully no longer be necessary after fog 2.0 112 | require self.fog_provider 113 | require 'carrierwave/storage/fog' 114 | Fog::Storage.new(fog_credentials) if fog_credentials.present? 115 | end 116 | 117 | def self.#{name}(value=nil) 118 | @#{name} = value if value 119 | eager_load_fog(value) if value && '#{name}' == 'fog_credentials' 120 | return @#{name} if self.object_id == #{self.object_id} || defined?(@#{name}) 121 | name = superclass.#{name} 122 | return nil if name.nil? && !instance_variable_defined?("@#{name}") 123 | @#{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 124 | end 125 | 126 | def self.#{name}=(value) 127 | eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present? 128 | @#{name} = value 129 | end 130 | 131 | def #{name}=(value) 132 | self.class.eager_load_fog(value) if '#{name}' == 'fog_credentials' && value.present? 133 | @#{name} = value 134 | end 135 | 136 | def #{name} 137 | value = @#{name} if instance_variable_defined?(:@#{name}) 138 | value = self.class.#{name} unless instance_variable_defined?(:@#{name}) 139 | if value.instance_of?(Proc) 140 | value.arity >= 1 ? value.call(self) : value.call 141 | else 142 | value 143 | end 144 | end 145 | RUBY 146 | end 147 | 148 | def configure 149 | yield self 150 | end 151 | 152 | ## 153 | # sets configuration back to default 154 | # 155 | def reset_config 156 | configure do |config| 157 | config.permissions = 0644 158 | config.directory_permissions = 0755 159 | config.storage_engines = { 160 | :file => "CarrierWave::Storage::File", 161 | :fog => "CarrierWave::Storage::Fog" 162 | } 163 | config.storage = :file 164 | config.cache_storage = :file 165 | config.fog_provider = 'fog' 166 | config.fog_attributes = {} 167 | config.fog_credentials = {} 168 | config.fog_public = true 169 | config.fog_authenticated_url_expiration = 600 170 | config.fog_use_ssl_for_aws = true 171 | config.store_dir = 'uploads' 172 | config.cache_dir = 'uploads/tmp' 173 | config.delete_tmp_file_after_storage = true 174 | config.move_to_cache = false 175 | config.move_to_store = false 176 | config.remove_previously_stored_files_after_update = true 177 | config.ignore_integrity_errors = true 178 | config.ignore_processing_errors = true 179 | config.ignore_download_errors = true 180 | config.validate_integrity = true 181 | config.validate_processing = true 182 | config.validate_download = true 183 | config.root = lambda { CarrierWave.root } 184 | config.base_path = CarrierWave.base_path 185 | config.enable_processing = true 186 | config.ensure_multipart_form = true 187 | end 188 | end 189 | end 190 | 191 | end 192 | end 193 | end 194 | 195 | -------------------------------------------------------------------------------- /spec/processing/mini_magick_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | describe CarrierWave::MiniMagick do 6 | 7 | before do 8 | @klass = Class.new(CarrierWave::Uploader::Base) do 9 | include CarrierWave::MiniMagick 10 | end 11 | @instance = @klass.new 12 | FileUtils.cp(file_path('landscape.jpg'), file_path('landscape_copy.jpg')) 13 | allow(@instance).to receive(:cached?).and_return true 14 | allow(@instance).to receive(:url).and_return nil 15 | allow(@instance).to receive(:file).and_return(CarrierWave::SanitizedFile.new(file_path('landscape_copy.jpg'))) 16 | end 17 | 18 | after do 19 | FileUtils.rm(file_path('landscape_copy.jpg')) if File.exist?(file_path('landscape_copy.jpg')) 20 | FileUtils.rm(file_path('landscape_copy.png')) if File.exist?(file_path('landscape_copy.png')) 21 | end 22 | 23 | describe "#convert" do 24 | it "should convert from one format to another" do 25 | @instance.convert('png') 26 | img = ::MiniMagick::Image.open(@instance.current_path) 27 | expect(img['format']).to match(/PNG/) 28 | expect(@instance.file.extension).to eq('png') 29 | end 30 | 31 | it "should convert all pages when no page number is specified" do 32 | expect_any_instance_of(::MiniMagick::Image).to receive(:format).with('png', nil).once 33 | @instance.convert('png') 34 | end 35 | 36 | it "should convert specific page" do 37 | expect_any_instance_of(::MiniMagick::Image).to receive(:format).with('png', 1).once 38 | @instance.convert('png', 1) 39 | end 40 | end 41 | 42 | describe '#resize_to_fill' do 43 | it "should resize the image to exactly the given dimensions and maintain file type" do 44 | @instance.resize_to_fill(200, 200) 45 | expect(@instance).to have_dimensions(200, 200) 46 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/JPEG/) 47 | end 48 | 49 | it "should resize the image to exactly the given dimensions and maintain updated file type" do 50 | @instance.convert('png') 51 | @instance.resize_to_fill(200, 200) 52 | expect(@instance).to have_dimensions(200, 200) 53 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/PNG/) 54 | expect(@instance.file.extension).to eq('png') 55 | end 56 | 57 | it "should scale up the image if it smaller than the given dimensions" do 58 | @instance.resize_to_fill(1000, 1000) 59 | expect(@instance).to have_dimensions(1000, 1000) 60 | end 61 | end 62 | 63 | describe '#resize_and_pad' do 64 | it "should resize the image to exactly the given dimensions and maintain file type" do 65 | @instance.resize_and_pad(200, 200) 66 | expect(@instance).to have_dimensions(200, 200) 67 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/JPEG/) 68 | end 69 | 70 | it "should resize the image to exactly the given dimensions and maintain updated file type" do 71 | @instance.convert('png') 72 | @instance.resize_and_pad(200, 200) 73 | expect(@instance).to have_dimensions(200, 200) 74 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/PNG/) 75 | end 76 | 77 | it "should scale up the image if it smaller than the given dimensions" do 78 | @instance.resize_and_pad(1000, 1000) 79 | expect(@instance).to have_dimensions(1000, 1000) 80 | end 81 | 82 | it "should pad with white" do 83 | @instance.resize_and_pad(200, 200) 84 | color = color_of_pixel(@instance.current_path, 0, 0) 85 | expect(color).to include('#FFFFFF') 86 | expect(color).not_to include('#FFFFFF00') 87 | end 88 | 89 | it "should pad with transparent" do 90 | @instance.convert('png') 91 | @instance.resize_and_pad(200, 200, :transparent) 92 | color = color_of_pixel(@instance.current_path, 0, 0) 93 | expect(color).to include('#FFFFFF00') 94 | end 95 | 96 | it "should not pad with transparent" do 97 | @instance.resize_and_pad(200, 200, :transparent) 98 | @instance.convert('png') 99 | color = color_of_pixel(@instance.current_path, 0, 0) 100 | expect(color).to include('#FFFFFF') 101 | expect(color).not_to include('#FFFFFF00') 102 | end 103 | 104 | end 105 | 106 | describe '#resize_to_fit' do 107 | it "should resize the image to fit within the given dimensions and maintain file type" do 108 | @instance.resize_to_fit(200, 200) 109 | expect(@instance).to have_dimensions(200, 150) 110 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/JPEG/) 111 | end 112 | 113 | it "should resize the image to fit within the given dimensions and maintain updated file type" do 114 | @instance.convert('png') 115 | @instance.resize_to_fit(200, 200) 116 | expect(@instance).to have_dimensions(200, 150) 117 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/PNG/) 118 | expect(@instance.file.extension).to eq('png') 119 | end 120 | 121 | it "should scale up the image if it smaller than the given dimensions" do 122 | @instance.resize_to_fit(1000, 1000) 123 | expect(@instance).to have_dimensions(1000, 750) 124 | end 125 | end 126 | 127 | describe '#resize_to_limit' do 128 | it "should resize the image to fit within the given dimensions and maintain file type" do 129 | @instance.resize_to_limit(200, 200) 130 | expect(@instance).to have_dimensions(200, 150) 131 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/JPEG/) 132 | end 133 | 134 | it "should resize the image to fit within the given dimensions and maintain updated file type" do 135 | @instance.convert('png') 136 | @instance.resize_to_limit(200, 200) 137 | expect(@instance).to have_dimensions(200, 150) 138 | expect(::MiniMagick::Image.open(@instance.current_path)['format']).to match(/PNG/) 139 | expect(@instance.file.extension).to eq('png') 140 | end 141 | 142 | it "should not scale up the image if it smaller than the given dimensions" do 143 | @instance.resize_to_limit(1000, 1000) 144 | expect(@instance).to have_dimensions(640, 480) 145 | end 146 | end 147 | 148 | describe "test errors" do 149 | context "invalid image file" do 150 | before do 151 | File.open(@instance.current_path, 'w') do |f| 152 | f.puts "bogus" 153 | end 154 | end 155 | 156 | it "should fail to process a non image file" do 157 | expect {@instance.resize_to_limit(200, 200)}.to raise_exception(CarrierWave::ProcessingError, /^Failed to manipulate with MiniMagick, maybe it is not an image\?/) 158 | end 159 | 160 | it "should use I18n" do 161 | change_locale_and_store_translations(:nl, :errors => { 162 | :messages => { 163 | :mini_magick_processing_error => "Kon bestand niet met MiniMagick bewerken, misschien is het geen beeld bestand?" 164 | } 165 | }) do 166 | expect {@instance.resize_to_limit(200, 200)}.to raise_exception(CarrierWave::ProcessingError, /^Kon bestand niet met MiniMagick bewerken, misschien is het geen beeld bestand\?/) 167 | end 168 | end 169 | 170 | it "should not suppress errors when translation is unavailable" do 171 | change_locale_and_store_translations(:foo, {}) do 172 | expect do 173 | @instance.resize_to_limit(200, 200) 174 | end.to raise_exception( CarrierWave::ProcessingError ) 175 | end 176 | end 177 | end 178 | end 179 | 180 | describe "return_width_and_height" do 181 | it "should return the width and height of the image" do 182 | @instance.resize_to_fill(200, 300) 183 | expect(@instance.width).to eq(200) 184 | expect(@instance.height).to eq(300) 185 | end 186 | end 187 | 188 | end 189 | --------------------------------------------------------------------------------