├── spec ├── spec.opts ├── spec_helper.rb ├── espeak_spec.rb └── easy_captcha_spec.rb ├── init.rb ├── Gemfile ├── lib ├── easy_captcha │ ├── version.rb │ ├── generator.rb │ ├── routes.rb │ ├── generator │ │ ├── base.rb │ │ └── default.rb │ ├── view_helpers.rb │ ├── captcha.rb │ ├── model_helpers.rb │ ├── captcha_controller.rb │ ├── espeak.rb │ └── controller_helpers.rb ├── generators │ ├── easy_captcha │ │ └── install_generator.rb │ └── templates │ │ └── easy_captcha.rb └── easy_captcha.rb ├── resources └── captcha.ttf ├── .travis.yml ├── .gitignore ├── Rakefile ├── LICENSE.txt ├── easy_captcha.gemspec ├── Gemfile.lock └── README.rdoc /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'easy_captcha' -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/easy_captcha/version.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | VERSION = '0.6.5' 3 | end 4 | -------------------------------------------------------------------------------- /resources/captcha.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phatworx/easy_captcha/HEAD/resources/captcha.ttf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.2 3 | - 1.9.3 4 | - jruby 5 | notifications: 6 | recipients: 7 | - develop@marco-scholl.de 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # jeweler generated 12 | pkg 13 | 14 | .DS_Store -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | RSpec::Core::RakeTask.new(:spec) 4 | 5 | task default: :spec 6 | 7 | require 'yard' 8 | YARD::Rake::YardocTask.new 9 | -------------------------------------------------------------------------------- /lib/easy_captcha/generator.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | # module for generators 3 | module Generator 4 | autoload :Base, 'easy_captcha/generator/base' 5 | autoload :Default, 'easy_captcha/generator/default' 6 | 7 | end 8 | end -------------------------------------------------------------------------------- /lib/easy_captcha/routes.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch #:nodoc: 2 | module Routing #:nodoc: 3 | class Mapper #:nodoc: 4 | # call to add default captcha root 5 | def captcha_route 6 | match 'captcha' => 'easy_captcha/captcha#captcha', :via => :get 7 | end 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /lib/easy_captcha/generator/base.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | module Generator 3 | 4 | # base class for generators 5 | class Base 6 | 7 | # generator for captcha images 8 | def initialize(&block) 9 | defaults 10 | yield self if block_given? 11 | end 12 | 13 | # default values for generator 14 | def defaults 15 | end 16 | 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/easy_captcha/view_helpers.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | # helper class for ActionView 3 | module ViewHelpers 4 | # generate an image_tag for captcha image 5 | def captcha_tag(*args) 6 | options = { :alt => 'captcha', :width => EasyCaptcha.image_width, :height => EasyCaptcha.image_height } 7 | options.merge! args.extract_options! 8 | image_tag(captcha_url(:i => Time.now.to_i), options) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | begin 4 | require 'simplecov' 5 | SimpleCov.start 'rails' 6 | rescue LoadError 7 | end 8 | require 'easy_captcha' 9 | 10 | # Requires supporting files with custom matchers and macros, etc, 11 | # in ./support/ and its subdirectories. 12 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 13 | -------------------------------------------------------------------------------- /lib/easy_captcha/captcha.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | module EasyCaptcha 3 | # captcha generation class 4 | class Captcha 5 | # code for captcha generation 6 | attr_reader :code 7 | # blob of generated captcha image 8 | attr_reader :image 9 | 10 | # generate captcha by code 11 | def initialize code 12 | @code = code 13 | generate_captcha 14 | end 15 | 16 | def inspect #:nodoc: 17 | "" 18 | end 19 | 20 | private 21 | 22 | def generate_captcha #:nodoc: 23 | @image = EasyCaptcha.generator.generate(@code) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/espeak_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 3 | 4 | describe EasyCaptcha::Espeak do 5 | context 'check default values' do 6 | subject { EasyCaptcha::Espeak.new } 7 | 8 | its(:amplitude) { (80..120).should include(subject.amplitude) } 9 | its(:pitch) { (30..70).should include(subject.pitch) } 10 | its(:gap) { should eq 80 } 11 | its(:voice) { should be_nil } 12 | end 13 | 14 | context 'check config: voices' do 15 | let(:voices) { ['german', 'german+m1'] } 16 | subject do 17 | EasyCaptcha::Espeak.new do |config| 18 | config.voice = voices 19 | end 20 | end 21 | it { voices.should include(subject.voice) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/generators/easy_captcha/install_generator.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | module Generators #:nodoc: 3 | class InstallGenerator < Rails::Generators::Base #:nodoc: 4 | source_root File.expand_path("../../templates", __FILE__) 5 | 6 | desc "Install easy_captcha" 7 | 8 | def copy_initializer #:nodoc: 9 | template "easy_captcha.rb", "config/initializers/easy_captcha.rb" 10 | end 11 | 12 | def add_devise_routes #:nodoc: 13 | route 'captcha_route' 14 | end 15 | 16 | def add_after_filter #:nodoc: 17 | inject_into_class "app/controllers/application_controller.rb", ApplicationController do 18 | " # reset captcha code after each request for security\n after_filter :reset_last_captcha_code!\n\n" 19 | end 20 | end 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /lib/easy_captcha/model_helpers.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | module ModelHelpers #:nodoc: 3 | # helper class for ActiveRecord 4 | def self.included(base) #:nodoc: 5 | base.extend ClassMethods 6 | end 7 | 8 | module ClassMethods #:nodoc: 9 | # to activate model captcha validation 10 | def acts_as_easy_captcha 11 | include InstanceMethods 12 | attr_writer :captcha, :captcha_verification 13 | end 14 | end 15 | 16 | module InstanceMethods #:nodoc: 17 | 18 | def captcha #:nodoc: 19 | "" 20 | end 21 | 22 | # validate captcha 23 | def captcha_valid? 24 | errors.add(:captcha, :invalid) if @captcha.blank? or @captcha_verification.blank? or @captcha.to_s.upcase != @captcha_verification.to_s.upcase 25 | end 26 | alias_method :valid_captcha?, :captcha_valid? 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/easy_captcha/captcha_controller.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | # captcha controller 3 | class CaptchaController < ActionController::Base 4 | before_filter :overwrite_cache_control 5 | # captcha action send the generated image to browser 6 | def captcha 7 | if params[:format] == "wav" and EasyCaptcha.espeak? 8 | send_data generate_speech_captcha, :disposition => 'inline', :type => 'audio/wav' 9 | else 10 | send_data generate_captcha, :disposition => 'inline', :type => 'image/png' 11 | end 12 | end 13 | 14 | private 15 | # Overwrite cache control for Samsung Galaxy S3 (remove no-store) 16 | def overwrite_cache_control 17 | response.headers["Cache-Control"] = "no-cache, max-age=0, must-revalidate" 18 | response.headers["Pragma"] = "no-cache" 19 | response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Marco Scholl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /easy_captcha.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "easy_captcha/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = %q{easy_captcha} 7 | s.version = EasyCaptcha::VERSION 8 | 9 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 10 | s.authors = [%q{Marco Scholl}, %q{Alexander Dreher}] 11 | s.date = %q{2011-09-15} 12 | s.description = %q{Captcha-Plugin for Rails} 13 | s.email = %q{team@phatworx.de} 14 | s.extra_rdoc_files = [ 15 | "LICENSE.txt", 16 | "README.rdoc" 17 | ] 18 | s.files = `git ls-files`.split("\n") 19 | 20 | s.homepage = %q{http://github.com/phatworx/easy_captcha} 21 | s.licenses = [%q{MIT}] 22 | s.rubygems_version = %q{1.8.15} 23 | s.summary = %q{Captcha-Plugin for Rails} 24 | 25 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 26 | s.test_files = `git ls-files -- {spec}/*`.split("\n") 27 | s.require_paths = ["lib"] 28 | 29 | s.add_dependency('rails', [">= 3.0.0"]) 30 | s.add_dependency('bundler', [">= 1.1.0"]) 31 | s.add_dependency('simplecov', [">= 0.3.8"]) 32 | s.add_dependency('rspec-rails', [">= 2.8.1"]) 33 | s.add_dependency('yard', [">= 0.7.0"]) 34 | 35 | if defined?(PLATFORM) && PLATFORM == 'java' 36 | s.add_runtime_dependency('rmagick4j','>= 0.3.7') 37 | else 38 | s.add_runtime_dependency('rmagick','>= 2.13.1') 39 | end 40 | end 41 | 42 | -------------------------------------------------------------------------------- /lib/easy_captcha/espeak.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | # espeak wrapper 3 | class Espeak 4 | 5 | # generator for captcha images 6 | def initialize(&block) 7 | defaults 8 | yield self if block_given? 9 | end 10 | 11 | # set default values 12 | def defaults 13 | @amplitude = 80..120 14 | @pitch = 30..70 15 | @gap = 80 16 | @voice = nil 17 | end 18 | 19 | attr_writer :amplitude, :pitch, :gap, :voice 20 | 21 | # return amplitude 22 | def amplitude 23 | if @amplitude.is_a? Range 24 | @amplitude.to_a.sort_by { rand }.first 25 | else 26 | @amplitude.to_i 27 | end 28 | end 29 | 30 | # return amplitude 31 | def pitch 32 | if @pitch.is_a? Range 33 | @pitch.to_a.sort_by { rand }.first 34 | else 35 | @pitch.to_i 36 | end 37 | end 38 | 39 | def gap 40 | @gap.to_i 41 | end 42 | 43 | def voice 44 | if @voice.is_a? Array 45 | v = @voice.sort_by { rand }.first 46 | else 47 | v = @voice 48 | end 49 | 50 | v.try :gsub, /[^A-Za-z0-9\-\+]/, "" 51 | end 52 | 53 | # generate wav file by captcha 54 | def generate(captcha, wav_file) 55 | # get code 56 | if captcha.is_a? Captcha 57 | code = captcha.code 58 | elsif captcha.is_a? String 59 | code = captcha 60 | else 61 | raise ArgumentError, "invalid captcha" 62 | end 63 | 64 | # add spaces 65 | code = code.each_char.to_a.join(" ") 66 | 67 | cmd = "espeak -g 10" 68 | cmd << " -a #{amplitude}" unless @amplitude.nil? 69 | cmd << " -p #{pitch}" unless @pitch.nil? 70 | cmd << " -g #{gap}" unless @gap.nil? 71 | cmd << " -v '#{voice}'" unless @voice.nil? 72 | cmd << " -w #{wav_file} '#{code}'" 73 | 74 | %x{#{cmd}} 75 | true 76 | end 77 | 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/generators/templates/easy_captcha.rb: -------------------------------------------------------------------------------- 1 | EasyCaptcha.setup do |config| 2 | # Cache 3 | # config.cache = true 4 | # Cache temp dir from Rails.root 5 | # config.cache_temp_dir = Rails.root + 'tmp' + 'captchas' 6 | # Cache size 7 | # config.cache_size = 500 8 | # Cache expire 9 | # config.cache_expire = 1.days 10 | 11 | # Chars 12 | # config.chars = %w(2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z) 13 | 14 | # Length 15 | # config.length = 6 16 | 17 | # Image 18 | # config.image_height = 40 19 | # config.image_width = 140 20 | 21 | # eSpeak 22 | # config.espeak do |espeak| 23 | # Amplitude, 0 to 200 24 | # espeak.amplitude = 80..120 25 | 26 | # Word gap. Pause between words 27 | # espeak.gap = 80 28 | 29 | # Pitch adjustment, 0 to 99 30 | # espeak.pitch = 30..70 31 | 32 | # Use voice file of this name from espeak-data/voices 33 | # espeak.voice = nil 34 | # end 35 | 36 | # configure generator 37 | # config.generator :default do |generator| 38 | 39 | # Font 40 | # generator.font_size = 24 41 | # generator.font_fill_color = '#333333' 42 | # generator.font_stroke_color = '#000000' 43 | # generator.font_stroke = 0 44 | # generator.font_family = File.expand_path('../../resources/afont.ttf', __FILE__) 45 | 46 | # generator.image_background_color = "#FFFFFF" 47 | 48 | # Wave 49 | # generator.wave = true 50 | # generator.wave_length = (60..100) 51 | # generator.wave_amplitude = (3..5) 52 | 53 | # Sketch 54 | # generator.sketch = true 55 | # generator.sketch_radius = 3 56 | # generator.sketch_sigma = 1 57 | 58 | # Implode 59 | # generator.implode = 0.1 60 | 61 | # Blur 62 | # generator.blur = true 63 | # generator.blur_radius = 1 64 | # generator.blur_sigma = 2 65 | # end 66 | end 67 | -------------------------------------------------------------------------------- /lib/easy_captcha/controller_helpers.rb: -------------------------------------------------------------------------------- 1 | module EasyCaptcha 2 | # helper class for ActionController 3 | module ControllerHelpers 4 | 5 | def self.included(base) #:nodoc: 6 | base.class_eval do 7 | helper_method :valid_captcha?, :captcha_valid? 8 | end 9 | end 10 | 11 | # generate captcha image and return it as blob 12 | def generate_captcha 13 | if EasyCaptcha.cache 14 | # create cache dir 15 | FileUtils.mkdir_p(EasyCaptcha.cache_temp_dir) 16 | 17 | # select all generated captchas from cache 18 | files = Dir.glob(EasyCaptcha.cache_temp_dir + "*.png") 19 | 20 | unless files.size < EasyCaptcha.cache_size 21 | file = File.open(files.at(Kernel.rand(files.size))) 22 | session[:captcha] = File.basename(file.path, '.*') 23 | 24 | if file.mtime < EasyCaptcha.cache_expire.ago 25 | File.unlink(file.path) 26 | # remove speech version 27 | File.unlink(file.path.gsub(/png\z/, "wav")) if File.exists?(file.path.gsub(/png\z/, "wav")) 28 | else 29 | return file.readlines.join 30 | end 31 | end 32 | generated_code = generate_captcha_code 33 | image = Captcha.new(generated_code).image 34 | 35 | # write captcha for caching 36 | File.open(captcha_cache_path(generated_code), 'w') { |f| f.write image } 37 | 38 | # write speech file if u create a new captcha image 39 | EasyCaptcha.espeak.generate(generated_code, speech_captcha_cache_path(generated_code)) if EasyCaptcha.espeak? 40 | 41 | # return image 42 | image 43 | else 44 | Captcha.new(generate_captcha_code).image 45 | end 46 | end 47 | 48 | # generate speech by captcha from session 49 | def generate_speech_captcha 50 | raise RuntimeError, "espeak disabled" unless EasyCaptcha.espeak? 51 | if EasyCaptcha.cache 52 | File.read(speech_captcha_cache_path(current_captcha_code)) 53 | else 54 | wav_file = Tempfile.new("#{current_captcha_code}.wav") 55 | EasyCaptcha.espeak.generate(current_captcha_code, wav_file.path) 56 | File.read(wav_file.path) 57 | end 58 | end 59 | 60 | # return cache path of captcha image 61 | def captcha_cache_path(code) 62 | "#{EasyCaptcha.cache_temp_dir}/#{code}.png" 63 | end 64 | 65 | # return cache path of speech captcha 66 | def speech_captcha_cache_path(code) 67 | "#{EasyCaptcha.cache_temp_dir}/#{code}.wav" 68 | end 69 | 70 | # current active captcha from session 71 | def current_captcha_code 72 | session[:captcha] 73 | end 74 | 75 | # generate captcha code, save in session and return 76 | def generate_captcha_code 77 | session[:captcha] = EasyCaptcha.length.times.collect { EasyCaptcha.chars[rand(EasyCaptcha.chars.size)] }.join 78 | end 79 | 80 | # validate given captcha code and re 81 | def captcha_valid?(code) 82 | return false if session[:captcha].blank? or code.blank? 83 | session[:captcha].to_s.upcase == code.to_s.upcase 84 | end 85 | alias_method :valid_captcha?, :captcha_valid? 86 | 87 | # reset the captcha code in session for security after each request 88 | def reset_last_captcha_code! 89 | session.delete(:captcha) 90 | end 91 | 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/easy_captcha_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | describe EasyCaptcha do 4 | describe :setup do 5 | EasyCaptcha.setup do |config| 6 | # Cache 7 | config.cache = false 8 | 9 | # Chars 10 | config.chars = %w(2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z) 11 | 12 | # Length 13 | config.length = 6 14 | 15 | # Image 16 | config.image_height = 40 17 | config.image_width = 140 18 | 19 | # configure generator 20 | config.generator :default do |generator| 21 | # Font 22 | generator.font_size = 24 23 | generator.font_fill_color = '#333333' 24 | generator.font_stroke_color = '#000000' 25 | generator.font_stroke = 0 26 | generator.font_family = File.expand_path('../../resources/afont.ttf', __FILE__) 27 | 28 | generator.image_background_color = '#FFFFFF' 29 | 30 | # Wave 31 | generator.wave = true 32 | generator.wave_length = (60..100) 33 | generator.wave_amplitude = (3..5) 34 | 35 | # Sketch 36 | generator.sketch = true 37 | generator.sketch_radius = 3 38 | generator.sketch_sigma = 1 39 | 40 | # Implode 41 | generator.implode = 0.1 42 | 43 | # Blur 44 | generator.blur = true 45 | generator.blur_radius = 1 46 | generator.blur_sigma = 2 47 | end 48 | end 49 | 50 | it 'sould not cache' do 51 | EasyCaptcha.cache?.should be_false 52 | end 53 | 54 | it 'should have default generator' do 55 | EasyCaptcha.generator.should be_an(EasyCaptcha::Generator::Default) 56 | end 57 | 58 | describe :depracations do 59 | before do 60 | EasyCaptcha.setup do |config| 61 | # Length 62 | config.length = 6 63 | 64 | # Image 65 | config.image_height = 40 66 | config.image_width = 140 67 | 68 | config.generator :default do |generator| 69 | # Font 70 | generator.font_size = 24 71 | generator.font_fill_color = '#333333' 72 | generator.font_stroke_color = '#000000' 73 | generator.font_stroke = 0 74 | generator.font_family = File.expand_path('../../resources/afont.ttf', __FILE__) 75 | 76 | generator.image_background_color = '#FFFFFF' 77 | 78 | # Wave 79 | generator.wave = true 80 | generator.wave_length = (60..100) 81 | generator.wave_amplitude = (3..5) 82 | 83 | # Sketch 84 | generator.sketch = true 85 | generator.sketch_radius = 3 86 | generator.sketch_sigma = 1 87 | 88 | # Implode 89 | generator.implode = 0.1 90 | 91 | # Blur 92 | generator.blur = true 93 | generator.blur_radius = 1 94 | generator.blur_sigma = 2 95 | end 96 | end 97 | end 98 | 99 | it 'get config' do 100 | [ 101 | :font_size, :font_fill_color, :font_family, :font_stroke, :font_stroke_color, 102 | :image_background_color, :sketch, :sketch_radius, :sketch_sigma, :wave, 103 | :wave_length, :wave_amplitude, :implode, :blur, :blur_radius, :blur_sigma 104 | ].each do |method| 105 | EasyCaptcha.generator.send(method).should_not be_nil 106 | end 107 | end 108 | 109 | it 'method_missing should call normal on non depracations' do 110 | -> { EasyCaptcha.send('a_missing_method') }.should raise_error 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/easy_captcha.rb: -------------------------------------------------------------------------------- 1 | require 'rails' 2 | require 'action_controller' 3 | require 'active_record' 4 | require 'active_support' 5 | 6 | # Captcha-Plugin for Rails 7 | module EasyCaptcha 8 | autoload :Espeak, 'easy_captcha/espeak' 9 | autoload :Captcha, 'easy_captcha/captcha' 10 | autoload :CaptchaController, 'easy_captcha/captcha_controller' 11 | autoload :ModelHelpers, 'easy_captcha/model_helpers' 12 | autoload :ViewHelpers, 'easy_captcha/view_helpers' 13 | autoload :ControllerHelpers, 'easy_captcha/controller_helpers' 14 | autoload :Generator, 'easy_captcha/generator' 15 | 16 | # Cache 17 | mattr_accessor :cache 18 | @@cache = false 19 | 20 | # Cache temp 21 | mattr_accessor :cache_temp_dir 22 | @@cache_temp_dir = nil 23 | 24 | # Cache size 25 | mattr_accessor :cache_size 26 | @@cache_size = 500 27 | 28 | # Cache expire 29 | mattr_accessor :cache_expire 30 | @@cache_expire = nil 31 | 32 | # Chars 33 | mattr_accessor :chars 34 | @@chars = %w(2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z) 35 | 36 | # Length 37 | mattr_accessor :length 38 | @@length = 6 39 | 40 | # Length 41 | mattr_accessor :image_width, :image_height 42 | @@image_width = 140 43 | @@image_height = 40 44 | 45 | class << self 46 | # to configure easy_captcha 47 | # for a sample look the readme.rdoc file 48 | def setup 49 | yield self 50 | end 51 | 52 | def cache? #:nodoc: 53 | cache 54 | end 55 | 56 | # select generator and configure this 57 | def generator(generator = nil, &block) 58 | if generator.nil? 59 | @generator 60 | else 61 | generator = generator.to_s if generator.is_a? Symbol 62 | 63 | if generator.is_a? String 64 | generator.gsub!(/^[a-z]|\s+[a-z]/) { |a| a.upcase } 65 | generator = "EasyCaptcha::Generator::#{generator}".constantize 66 | end 67 | 68 | @generator = generator.new &block 69 | end 70 | end 71 | 72 | def espeak=(state) 73 | if state === true 74 | @espeak = Espeak.new 75 | else 76 | @espeak = false 77 | end 78 | end 79 | 80 | def espeak(&block) 81 | if block_given? 82 | @espeak = Espeak.new &block 83 | else 84 | @espeak ||= false 85 | end 86 | end 87 | 88 | def espeak? 89 | not espeak === false 90 | end 91 | 92 | # depracated 93 | def method_missing name, *args 94 | name = name.to_s # fix for jruby 95 | depracations = [ 96 | :font_size, :font_fill_color, :font_family, :font_stroke, :font_stroke_color, 97 | :image_background_color, :sketch, :sketch_radius, :sketch_sigma, :wave, 98 | :wave_length, :wave_amplitude, :implode, :blur, :blur_radius, :blur_sigma 99 | ] 100 | 101 | if depracations.include? name[0..-2].to_sym or depracations.include? name.to_sym 102 | ActiveSupport::Deprecation.warn "EasyCaptcha.#{name} is deprecated." 103 | if name[-1,1] == '=' 104 | self.generator.send(name, args.first) 105 | else 106 | self.generator.send(name) 107 | end 108 | else 109 | super 110 | end 111 | end 112 | 113 | 114 | # called by rails after initialize 115 | def init 116 | require 'easy_captcha/routes' 117 | ActiveRecord::Base.send :include, ModelHelpers 118 | ActionController::Base.send :include, ControllerHelpers 119 | ActionView::Base.send :include, ViewHelpers 120 | 121 | # set default generator 122 | generator :default 123 | 124 | end 125 | end 126 | end 127 | 128 | EasyCaptcha.init 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | easy_captcha (0.6.5) 5 | bundler (>= 1.1.0) 6 | rails (>= 3.0.0) 7 | rmagick (>= 2.13.1) 8 | rspec-rails (>= 2.8.1) 9 | simplecov (>= 0.3.8) 10 | yard (>= 0.7.0) 11 | 12 | GEM 13 | remote: http://rubygems.org/ 14 | specs: 15 | actionmailer (4.2.3) 16 | actionpack (= 4.2.3) 17 | actionview (= 4.2.3) 18 | activejob (= 4.2.3) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 1.0, >= 1.0.5) 21 | actionpack (4.2.3) 22 | actionview (= 4.2.3) 23 | activesupport (= 4.2.3) 24 | rack (~> 1.6) 25 | rack-test (~> 0.6.2) 26 | rails-dom-testing (~> 1.0, >= 1.0.5) 27 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 28 | actionview (4.2.3) 29 | activesupport (= 4.2.3) 30 | builder (~> 3.1) 31 | erubis (~> 2.7.0) 32 | rails-dom-testing (~> 1.0, >= 1.0.5) 33 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 34 | activejob (4.2.3) 35 | activesupport (= 4.2.3) 36 | globalid (>= 0.3.0) 37 | activemodel (4.2.3) 38 | activesupport (= 4.2.3) 39 | builder (~> 3.1) 40 | activerecord (4.2.3) 41 | activemodel (= 4.2.3) 42 | activesupport (= 4.2.3) 43 | arel (~> 6.0) 44 | activesupport (4.2.3) 45 | i18n (~> 0.7) 46 | json (~> 1.7, >= 1.7.7) 47 | minitest (~> 5.1) 48 | thread_safe (~> 0.3, >= 0.3.4) 49 | tzinfo (~> 1.1) 50 | arel (6.0.3) 51 | builder (3.2.2) 52 | diff-lcs (1.2.5) 53 | docile (1.1.5) 54 | erubis (2.7.0) 55 | globalid (0.3.6) 56 | activesupport (>= 4.1.0) 57 | i18n (0.7.0) 58 | json (1.8.3) 59 | loofah (2.0.2) 60 | nokogiri (>= 1.5.9) 61 | mail (2.6.3) 62 | mime-types (>= 1.16, < 3) 63 | mime-types (2.6.1) 64 | mini_portile (0.6.2) 65 | minitest (5.8.0) 66 | nokogiri (1.6.6.2) 67 | mini_portile (~> 0.6.0) 68 | rack (1.6.4) 69 | rack-test (0.6.3) 70 | rack (>= 1.0) 71 | rails (4.2.3) 72 | actionmailer (= 4.2.3) 73 | actionpack (= 4.2.3) 74 | actionview (= 4.2.3) 75 | activejob (= 4.2.3) 76 | activemodel (= 4.2.3) 77 | activerecord (= 4.2.3) 78 | activesupport (= 4.2.3) 79 | bundler (>= 1.3.0, < 2.0) 80 | railties (= 4.2.3) 81 | sprockets-rails 82 | rails-deprecated_sanitizer (1.0.3) 83 | activesupport (>= 4.2.0.alpha) 84 | rails-dom-testing (1.0.6) 85 | activesupport (>= 4.2.0.beta, < 5.0) 86 | nokogiri (~> 1.6.0) 87 | rails-deprecated_sanitizer (>= 1.0.1) 88 | rails-html-sanitizer (1.0.2) 89 | loofah (~> 2.0) 90 | railties (4.2.3) 91 | actionpack (= 4.2.3) 92 | activesupport (= 4.2.3) 93 | rake (>= 0.8.7) 94 | thor (>= 0.18.1, < 2.0) 95 | rake (10.4.2) 96 | rmagick (2.15.3) 97 | rspec-core (3.3.2) 98 | rspec-support (~> 3.3.0) 99 | rspec-expectations (3.3.1) 100 | diff-lcs (>= 1.2.0, < 2.0) 101 | rspec-support (~> 3.3.0) 102 | rspec-mocks (3.3.2) 103 | diff-lcs (>= 1.2.0, < 2.0) 104 | rspec-support (~> 3.3.0) 105 | rspec-rails (3.3.3) 106 | actionpack (>= 3.0, < 4.3) 107 | activesupport (>= 3.0, < 4.3) 108 | railties (>= 3.0, < 4.3) 109 | rspec-core (~> 3.3.0) 110 | rspec-expectations (~> 3.3.0) 111 | rspec-mocks (~> 3.3.0) 112 | rspec-support (~> 3.3.0) 113 | rspec-support (3.3.0) 114 | simplecov (0.10.0) 115 | docile (~> 1.1.0) 116 | json (~> 1.8) 117 | simplecov-html (~> 0.10.0) 118 | simplecov-html (0.10.0) 119 | sprockets (3.3.0) 120 | rack (~> 1.0) 121 | sprockets-rails (2.3.2) 122 | actionpack (>= 3.0) 123 | activesupport (>= 3.0) 124 | sprockets (>= 2.8, < 4.0) 125 | thor (0.19.1) 126 | thread_safe (0.3.5) 127 | tzinfo (1.2.2) 128 | thread_safe (~> 0.1) 129 | yard (0.8.7.6) 130 | 131 | PLATFORMS 132 | ruby 133 | 134 | DEPENDENCIES 135 | easy_captcha! 136 | 137 | BUNDLED WITH 138 | 1.10.3 139 | -------------------------------------------------------------------------------- /lib/easy_captcha/generator/default.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module EasyCaptcha 4 | module Generator 5 | 6 | # default generator 7 | class Default < Base 8 | 9 | # set default values 10 | def defaults 11 | @font_size = 24 12 | @font_fill_color = '#333333' 13 | @font = File.expand_path('../../../../resources/captcha.ttf', __FILE__) 14 | @font_stroke = '#000000' 15 | @font_stroke_color = 0 16 | @image_background_color = '#FFFFFF' 17 | @sketch = true 18 | @sketch_radius = 3 19 | @sketch_sigma = 1 20 | @wave = true 21 | @wave_length = (60..100) 22 | @wave_amplitude = (3..5) 23 | @implode = 0.05 24 | @blur = true 25 | @blur_radius = 1 26 | @blur_sigma = 2 27 | end 28 | 29 | # Font 30 | attr_accessor :font_size, :font_fill_color, :font, :font_family, :font_stroke, :font_stroke_color 31 | 32 | # Background 33 | attr_accessor :image_background_color, :background_image 34 | 35 | # Sketch 36 | attr_accessor :sketch, :sketch_radius, :sketch_sigma 37 | 38 | # Wave 39 | attr_accessor :wave, :wave_length, :wave_amplitude 40 | 41 | # Implode 42 | attr_accessor :implode 43 | 44 | # Gaussian Blur 45 | attr_accessor :blur, :blur_radius, :blur_sigma 46 | 47 | def sketch? #:nodoc: 48 | @sketch 49 | end 50 | 51 | def wave? #:nodoc: 52 | @wave 53 | end 54 | 55 | def blur? #:nodoc: 56 | @blur 57 | end 58 | 59 | # generate image 60 | def generate(code) 61 | require 'rmagick' unless defined?(Magick) 62 | 63 | config = self 64 | canvas = Magick::Image.new(EasyCaptcha.image_width, EasyCaptcha.image_height) do |variable| 65 | self.background_color = config.image_background_color unless config.image_background_color.nil? 66 | self.background_color = 'none' if config.background_image.present? 67 | end 68 | 69 | # Render the text in the image 70 | canvas.annotate(Magick::Draw.new, 0, 0, 0, 0, code) { 71 | self.gravity = Magick::CenterGravity 72 | self.font = config.font 73 | self.font_weight = Magick::LighterWeight 74 | self.fill = config.font_fill_color 75 | if config.font_stroke.to_i > 0 76 | self.stroke = config.font_stroke_color 77 | self.stroke_width = config.font_stroke 78 | end 79 | self.pointsize = config.font_size 80 | } 81 | 82 | # Blur 83 | canvas = canvas.blur_image(config.blur_radius, config.blur_sigma) if config.blur? 84 | 85 | # Wave 86 | w = config.wave_length 87 | a = config.wave_amplitude 88 | canvas = canvas.wave(rand(a.last - a.first) + a.first, rand(w.last - w.first) + w.first) if config.wave? 89 | 90 | # Sketch 91 | canvas = canvas.sketch(config.sketch_radius, config.sketch_sigma, rand(180)) if config.sketch? 92 | 93 | # Implode 94 | canvas = canvas.implode(config.implode.to_f) if config.implode.is_a? Float 95 | 96 | # Crop image because to big after waveing 97 | canvas = canvas.crop(Magick::CenterGravity, EasyCaptcha.image_width, EasyCaptcha.image_height) 98 | 99 | 100 | # Combine images if background image is present 101 | if config.background_image.present? 102 | background = Magick::Image.read(config.background_image).first 103 | background.composite!(canvas, Magick::CenterGravity, Magick::OverCompositeOp) 104 | 105 | image = background.to_blob { self.format = 'PNG' } 106 | else 107 | image = canvas.to_blob { self.format = 'PNG' } 108 | end 109 | 110 | # ruby-1.9 111 | image = image.force_encoding 'UTF-8' if image.respond_to? :force_encoding 112 | 113 | canvas.destroy! 114 | image 115 | end 116 | 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = EasyCaptcha 2 | A simple captcha implementation for rails 3 based on rmagick 3 | 4 | Tested with Rails 3.2.8 5 | 6 | https://travis-ci.org/phatworx/easy_captcha.png 7 | 8 | == Installation 9 | add to Gemfile 10 | gem 'easy_captcha' 11 | gem 'rmagick' 12 | 13 | for java you can use 14 | 15 | gem 'rmagick4j' 16 | 17 | after bundle execute 18 | rails g easy_captcha:install 19 | 20 | == Configuration 21 | You can configure easy_captcha in "config/initializers/easy_captcha.rb", if you want to customize the default configuration 22 | 23 | EasyCaptcha.setup do |config| 24 | # Cache 25 | # config.cache = true 26 | # Cache temp dir from Rails.root 27 | # config.cache_temp_dir = Rails.root.join('tmp', 'captchas') 28 | # Cache size 29 | # config.cache_size = 500 30 | # Cache expire 31 | # config.cache_expire = 1.day 32 | 33 | # Chars 34 | # config.chars = %w(2 3 4 5 6 7 9 A C D E F G H J K L M N P Q R S T U X Y Z) 35 | 36 | # Length 37 | # config.length = 6 38 | 39 | # Image 40 | # config.image_height = 40 41 | # config.image_width = 140 42 | 43 | # eSpeak (default disabled) 44 | # config.espeak do |espeak| 45 | # Amplitude, 0 to 200 46 | # espeak.amplitude = 80..120 47 | 48 | # Word gap. Pause between words 49 | # espeak.gap = 80 50 | 51 | # Pitch adjustment, 0 to 99 52 | # espeak.pitch = 30..70 53 | 54 | # Use voice file of this name from espeak-data/voices 55 | # espeak.voice = nil 56 | # end 57 | 58 | # configure generator 59 | # config.generator :default do |generator| 60 | 61 | # Font 62 | # generator.font_size = 24 63 | # generator.font_fill_color = '#333333' 64 | # generator.font_stroke_color = '#000000' 65 | # generator.font_stroke = 0 66 | # generator.font = File.expand_path('../../resources/afont.ttf', __FILE__) 67 | 68 | 69 | # Background color 70 | # generator.image_background_color = "#FFFFFF" 71 | # Or background image (e.g. transparent png) 72 | # generator.background_image = File.expand_path('../../resources/captcha_bg.png', __FILE__) 73 | 74 | # Wave 75 | # generator.wave = true 76 | # generator.wave_length = (60..100) 77 | # generator.wave_amplitude = (3..5) 78 | 79 | # Sketch 80 | # generator.sketch = true 81 | # generator.sketch_radius = 3 82 | # generator.sketch_sigma = 1 83 | 84 | # Implode 85 | # generator.implode = 0.1 86 | 87 | # Blur 88 | # generator.blur = true 89 | # generator.blur_radius = 1 90 | # generator.blur_sigma = 2 91 | # end 92 | end 93 | 94 | == Caching 95 | It is strongly recommended to enable caching. You can see the three paramters which you have to fill in your config file below. 96 | 97 | EasyCaptcha.setup do |config| 98 | # Cache 99 | config.cache = true 100 | # Cache temp dir from Rails.root 101 | config.cache_temp_dir = Rails.root.join('tmp', 'captchas') 102 | # Cache expire 103 | config.cache_expire = 1.day 104 | # Cache size 105 | # config.cache_size = 500 106 | end 107 | 108 | == Requirements 109 | 110 | * RMagick 111 | * Rails 3 (http://github.com/rails/rails) 112 | 113 | == Example 114 | 115 | <% form_tag '/' do %> 116 | <% if request.post? %> 117 |

<%= valid_captcha?(params[:captcha]) ? 'valid' : 'invalid' %> captcha

118 | <% end %> 119 |

<%= captcha_tag %>

120 |

<%= text_field_tag :captcha %>

121 |

<%= submit_tag 'Validate' %>

122 | <% end %> 123 | 124 | == Example app 125 | You find an example app under: http://github.com/phatworx/easy_captcha_example 126 | 127 | == History 128 | * 0.1 init 129 | * 0.2 cache support for high frequented sites 130 | * 0.3 use generators, optimizations, update licence to same of all my plugins 131 | * 0.4 generator support 132 | * 0.5 (transparent) background support 133 | * 0.6 espeak support for barrier-free support 134 | 135 | == Maintainers 136 | 137 | * Team Phatworx (http://github.com/phatworx) 138 | * Marco Scholl (http://github.com/traxanos) 139 | * Alexander Dreher (http://github.com/alexdreher) 140 | * Christoph Chilian (http://github.com/cc-web) 141 | 142 | == Contributing to EasyCaptcha 143 | 144 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 145 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 146 | * Fork the project 147 | * Start a feature/bugfix branch 148 | * Commit and push until you are happy with your contribution 149 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 150 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 151 | 152 | == Copyright 153 | 154 | Copyright (c) 2010 Marco Scholl. See LICENSE.txt for further details. 155 | --------------------------------------------------------------------------------