easy_captcha-ftbpro 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
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
+ "<EasyCaptcha::Captcha @code=#{code}>"
18
+ end
19
+
20
+ private
21
+
22
+ def generate_captcha #:nodoc:
23
+ @image = EasyCaptcha.generator.generate(@code)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module EasyCaptcha
2
+ # captcha controller
3
+ class Controller < 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
@@ -0,0 +1,93 @@
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
@@ -0,0 +1,79 @@
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
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+ module EasyCaptcha
3
+ module Generator
4
+
5
+ # default generator
6
+ class Default < Base
7
+
8
+ # set default values
9
+ def defaults
10
+ @font_size = 24
11
+ @font_fill_color = '#333333'
12
+ @font = File.expand_path('../../../../resources/captcha.ttf', __FILE__)
13
+ @font_stroke = '#000000'
14
+ @font_stroke_color = 0
15
+ @image_background_color = '#FFFFFF'
16
+ @sketch = true
17
+ @sketch_radius = 3
18
+ @sketch_sigma = 1
19
+ @wave = true
20
+ @wave_length = (60..100)
21
+ @wave_amplitude = (3..5)
22
+ @implode = 0.05
23
+ @blur = true
24
+ @blur_radius = 1
25
+ @blur_sigma = 2
26
+ end
27
+
28
+ # Font
29
+ attr_accessor :font_size, :font_fill_color, :font, :font_family, :font_stroke, :font_stroke_color
30
+
31
+ # Background
32
+ attr_accessor :image_background_color, :background_image
33
+
34
+ # Sketch
35
+ attr_accessor :sketch, :sketch_radius, :sketch_sigma
36
+
37
+ # Wave
38
+ attr_accessor :wave, :wave_length, :wave_amplitude
39
+
40
+ # Implode
41
+ attr_accessor :implode
42
+
43
+ # Gaussian Blur
44
+ attr_accessor :blur, :blur_radius, :blur_sigma
45
+
46
+ def sketch? #:nodoc:
47
+ @sketch
48
+ end
49
+
50
+ def wave? #:nodoc:
51
+ @wave
52
+ end
53
+
54
+ def blur? #:nodoc:
55
+ @blur
56
+ end
57
+
58
+ # generate image
59
+ def generate(code)
60
+ config = self
61
+ canvas = Magick::Image.new(EasyCaptcha.image_width, EasyCaptcha.image_height) do |variable|
62
+ self.background_color = config.image_background_color unless config.image_background_color.nil?
63
+ self.background_color = 'none' if config.background_image.present?
64
+ end
65
+
66
+ # Render the text in the image
67
+ canvas.annotate(Magick::Draw.new, 0, 0, 0, 0, code) {
68
+ self.gravity = Magick::CenterGravity
69
+ self.font = config.font
70
+ self.font_weight = Magick::LighterWeight
71
+ self.fill = config.font_fill_color
72
+ if config.font_stroke.to_i > 0
73
+ self.stroke = config.font_stroke_color
74
+ self.stroke_width = config.font_stroke
75
+ end
76
+ self.pointsize = config.font_size
77
+ }
78
+
79
+ # Blur
80
+ canvas = canvas.blur_image(config.blur_radius, config.blur_sigma) if config.blur?
81
+
82
+ # Wave
83
+ w = config.wave_length
84
+ a = config.wave_amplitude
85
+ canvas = canvas.wave(rand(a.last - a.first) + a.first, rand(w.last - w.first) + w.first) if config.wave?
86
+
87
+ # Sketch
88
+ canvas = canvas.sketch(config.sketch_radius, config.sketch_sigma, rand(180)) if config.sketch?
89
+
90
+ # Implode
91
+ canvas = canvas.implode(config.implode.to_f) if config.implode.is_a? Float
92
+
93
+ # Crop image because to big after waveing
94
+ canvas = canvas.crop(Magick::CenterGravity, EasyCaptcha.image_width, EasyCaptcha.image_height)
95
+
96
+
97
+ # Combine images if background image is present
98
+ if config.background_image.present?
99
+ background = Magick::Image.read(config.background_image).first
100
+ background.composite!(canvas, Magick::CenterGravity, Magick::OverCompositeOp)
101
+
102
+ image = background.to_blob { self.format = 'PNG' }
103
+ else
104
+ image = canvas.to_blob { self.format = 'PNG' }
105
+ end
106
+
107
+ # ruby-1.9
108
+ image = image.force_encoding 'UTF-8' if image.respond_to? :force_encoding
109
+
110
+ canvas.destroy!
111
+ image
112
+ end
113
+
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
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
@@ -0,0 +1,10 @@
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' => EasyCaptcha::Controller, :action => :captcha, :via => :get
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module EasyCaptcha
2
+ VERSION = '0.6.4'
3
+ end
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,23 @@
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