winton-captcha 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,23 +1,23 @@
1
1
  captcha
2
2
  =======
3
3
 
4
- A simple captcha generator for Rails.
5
-
6
-
7
- How it works
8
- ------------
9
-
10
- If **public/images/captchas** does not exist, 100 captchas will be generated there when the application starts.
4
+ An Rmagick based, Google-style captcha generator for Rails.
11
5
 
12
6
 
13
7
  Install
14
8
  -------
15
9
 
16
- gem install winton-captcha
10
+ script/plugin install git://github.com/winton/captcha.git
11
+
12
+ ### Generate config/captcha.rb
13
+
14
+ rake captcha
17
15
 
18
- ### Add to environment.rb
16
+ Take a look at the config file.
19
17
 
20
- config.gem 'winton-captcha', :lib => 'captcha', :source => 'http://gems.github.com'
18
+ ### Generate captchas
19
+
20
+ rake captcha:generate
21
21
 
22
22
  ### Add to .gitignore
23
23
 
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+
3
+ task :default => 'captcha.gemspec'
4
+
5
+ file 'captcha.gemspec' => FileList['{lib,spec}/**','Rakefile'] do |f|
6
+ # read spec file and split out manifest section
7
+ spec = File.read(f.name)
8
+ parts = spec.split(" # = MANIFEST =\n")
9
+ fail 'bad spec' if parts.length != 3
10
+ # determine file list from git ls-files
11
+ files = `git ls-files`.
12
+ split("\n").
13
+ sort.
14
+ reject{ |file| file =~ /^\./ }.
15
+ reject { |file| file =~ /^doc/ }.
16
+ map{ |file| " #{file}" }.
17
+ join("\n")
18
+ # piece file back together and write...
19
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
20
+ spec = parts.join(" # = MANIFEST =\n")
21
+ File.open(f.name, 'w') { |io| io.write(spec) }
22
+ puts "Updated #{f.name}"
23
+ end
24
+
25
+ # sudo rake install
26
+ task :install do
27
+ `sudo gem uninstall captcha -x`
28
+ `gem build captcha.gemspec`
29
+ `sudo gem install captcha*.gem`
30
+ `rm captcha*.gem`
31
+ end
data/captcha.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'captcha'
3
+ s.version = '1.0.4'
4
+ s.date = '2009-03-23'
5
+
6
+ s.summary = "An Rmagick based, Google-style captcha generator"
7
+ s.description = "An Rmagick based, Google-style captcha generator"
8
+
9
+ s.author = 'Winton Welsh'
10
+ s.email = 'mail@wintoni.us'
11
+ s.homepage = 'http://github.com/winton/captcha'
12
+
13
+ s.has_rdoc = false
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ README.markdown
18
+ Rakefile
19
+ captcha.gemspec
20
+ init.rb
21
+ lib/captcha.rb
22
+ lib/captcha/action.rb
23
+ lib/captcha/actions.rb
24
+ lib/captcha/captcha.rb
25
+ lib/captcha/config.rb
26
+ lib/captcha/generator.rb
27
+ lib/captcha/image.rb
28
+ lib/captcha/routes.rb
29
+ resources/captcha.ttf
30
+ resources/captchas.rb
31
+ spec/lib/captcha_spec.rb
32
+ spec/spec.opts
33
+ spec/spec_helper.rb
34
+ tasks/captcha.rake
35
+ ]
36
+ # = MANIFEST =
37
+ end
data/init.rb CHANGED
@@ -1 +1,7 @@
1
- require 'captcha'
1
+ require 'captcha'
2
+ if File.exists?("#{RAILS_ROOT}/lib/captcha_config.rb")
3
+ require "#{RAILS_ROOT}/lib/captcha_config"
4
+ end
5
+
6
+ ActionController::Base.send :include, Captcha::Action
7
+ Captcha::Generator.new
data/lib/captcha.rb CHANGED
@@ -1,59 +1,4 @@
1
- require 'RMagick'
2
-
3
- Dir[File.expand_path('*/*.rb', File.dirname(__FILE__))].each do |f|
4
- require [ File.dirname(f), File.basename(f, '.rb') ].join('/')
5
- end
6
-
7
- class Captcha
8
-
9
- include Magick
10
- attr_reader :code, :code_image
11
-
12
- JIGGLE = 15
13
- WOBBLE = 25
14
-
15
- def initialize(len)
16
- chars = ('a'..'z').to_a - ['a','e','i','o','u','l','j']
17
- code_array = []
18
- 1.upto(len) { code_array << chars[rand(chars.length)] }
19
- granite = Magick::ImageList.new('granite:')
20
- canvas = Magick::ImageList.new
21
- canvas.new_image(32*len, 50, Magick::TextureFill.new(granite))
22
- text = Magick::Draw.new
23
- text.font = File.expand_path('vendor/plugins/captcha/resources/captcha.ttf')
24
- text.pointsize = 40
25
- cur = 10
26
-
27
- code_array.each { |c|
28
- rot = rand(10) > 5 ? rand(WOBBLE) : -rand(WOBBLE)
29
- weight = rand(10) > 5 ? NormalWeight : BoldWeight
30
- text.annotate(canvas,0,0,cur,30+rand(JIGGLE), c) {
31
- self.rotation = rot
32
- self.font_weight = weight
33
- self.fill = '#98999B'
34
- }
35
- cur += 30
36
- }
37
-
38
- @code = code_array.to_s
39
- @code_image = canvas.to_blob {
40
- self.format = "JPG"
41
- }
42
-
43
- GC.start
44
- end
45
-
46
- end
47
-
48
- to = "public/images/captchas"
49
- unless File.exists?(to)
50
- FileUtils.mkdir_p to
51
- (0..99).each do |x|
52
- c = Captcha.new(6)
53
- File.open("#{to}/#{c.code}.jpg", 'w') do |f|
54
- f << c.code_image
55
- end
56
- end
57
- end
58
-
59
- ActionController::Base.send :include, CaptchaActions
1
+ require File.dirname(__FILE__) + "/captcha/action.rb"
2
+ require File.dirname(__FILE__) + "/captcha/image.rb"
3
+ require File.dirname(__FILE__) + "/captcha/config.rb"
4
+ require File.dirname(__FILE__) + "/captcha/generator.rb"
@@ -0,0 +1,42 @@
1
+ module Captcha
2
+ module Action
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_captcha
10
+ include Captcha::Action::InstanceMethods
11
+ self.around_filter CaptchaFilter.new
12
+ end
13
+ end
14
+
15
+ module InstanceMethods
16
+ def new
17
+ files = Dir["#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/*.jpg"]
18
+ @captcha = File.basename(files[rand(files.length)], '.jpg')
19
+ show
20
+ end
21
+
22
+ def show
23
+ new unless @captcha
24
+ send_file "#{RAILS_ROOT}/#{CAPTCHAS.options[:destination]}/#{@captcha}.jpg", :type => 'image/jpeg', :disposition => 'inline'
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ class CaptchaFilter
31
+ def before(controller)
32
+ @captcha = session[:captcha]
33
+ end
34
+ def after(controller)
35
+ if session[:captcha] != @captcha
36
+ session[:captcha] = @captcha
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,71 @@
1
+ module Captcha
2
+ class Config
3
+ def initialize(options={})
4
+ @@options = {
5
+ :colors => {
6
+ :background => '#1B1C20',
7
+ :font => '#7385C5'
8
+ },
9
+ # number of captcha images to generate
10
+ :count => 500,
11
+ :destination => File.expand_path(
12
+ (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/" : '') +
13
+ "public/images/captchas"
14
+ ),
15
+ :dimensions => {
16
+ # canvas height (px)
17
+ :height => 31,
18
+ # canvas width (px)
19
+ :width => 90
20
+ },
21
+ :generate_every => 24 * 60 * 60,
22
+ # http://www.imagemagick.org/RMagick/doc/image2.html#implode
23
+ :implode => 0.2,
24
+ :letters => {
25
+ # text baseline (px)
26
+ :baseline => 24,
27
+ # number of letters in captcha
28
+ :count => 6,
29
+ :ignore => ['a','e','i','o','u','l','j','q'],
30
+ # font size (pts)
31
+ :points => 34,
32
+ # width of a character (used to decrease or increase space between characters) (px)
33
+ :width => 14
34
+ },
35
+ :ttf => File.expand_path("#{File.dirname(__FILE__)}/../../resources/captcha.ttf"),
36
+ # http://www.imagemagick.org/RMagick/doc/image3.html#wave
37
+ :wave => {
38
+ # range is used for randomness (px)
39
+ :wavelength => (20..70),
40
+ # distance between peak and valley of sin wave (px)
41
+ :amplitude => 3
42
+ }
43
+ }.merge(options)
44
+ end
45
+
46
+ def self.captchas_ordered_by_modified
47
+ return unless @@options
48
+ files_modified = Dir["#{@@options[:destination]}/*.jpg"].collect do |file|
49
+ [ file, File.mtime(file) ]
50
+ end
51
+ # Youngest to oldest
52
+ files_modified.sort! { |a, b| b[1] <=> a[1] }
53
+ files_modified.collect { |f| f[0] }
54
+ end
55
+
56
+ def self.codes
57
+ self.captchas_ordered_by_modified.collect do |f|
58
+ File.basename f, '.jpg'
59
+ end
60
+ end
61
+
62
+ def self.options
63
+ @@options
64
+ end
65
+
66
+ def self.last_modified
67
+ youngest = self.captchas_ordered_by_modified.first
68
+ youngest ? File.mtime(youngest) : nil
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,29 @@
1
+ module Captcha
2
+ class Generator
3
+ def initialize
4
+ generate
5
+ end
6
+
7
+ def generate
8
+ return unless Config.options
9
+ return if Config.last_modified && Config.last_modified > Time.now - Config.options[:generate_every]
10
+ FileUtils.mkdir_p Config.options[:destination]
11
+ (1..Config.options[:count]).each do |x|
12
+ c = Image.new Config.options
13
+ File.open("#{Config.options[:destination]}/#{c.code}.jpg", 'w') do |f|
14
+ f << c.image
15
+ end
16
+ end
17
+ destroy_if_over_limit
18
+ GC.start
19
+ end
20
+
21
+ def destroy_if_over_limit
22
+ if Config.codes.length >= Config.options[:count] * 3
23
+ Config.captchas_ordered_by_modified[(Config.options[:count] * 2)..-1].each do |file|
24
+ FileUtils.rm_f(file)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'RMagick'
2
+
3
+ module Captcha
4
+ class Image
5
+
6
+ include Magick
7
+ attr_reader :code, :image
8
+
9
+ def initialize(o)
10
+ @code = generate_code o
11
+
12
+ canvas = Magick::ImageList.new
13
+ canvas.new_image(o[:dimensions][:width], o[:dimensions][:height]) {
14
+ self.background_color = o[:colors][:background]
15
+ }
16
+
17
+ text = Magick::Draw.new
18
+ text.font = File.expand_path o[:ttf]
19
+ text.pointsize = o[:letters][:points]
20
+
21
+ cur = 0
22
+ @code.each { |c|
23
+ text.annotate(canvas, 0, 0, cur, o[:letters][:baseline], c) {
24
+ self.fill = o[:colors][:font]
25
+ }
26
+ cur += o[:letters][:width]
27
+ }
28
+
29
+ w = o[:wave][:wavelength]
30
+ canvas = canvas.wave(o[:wave][:amplitude], rand(w.last - w.first) + w.first)
31
+ canvas = canvas.implode(o[:implode])
32
+
33
+ @code = @code.to_s
34
+ @image = canvas.to_blob { self.format = "JPG" }
35
+ end
36
+
37
+ private
38
+
39
+ def generate_code(o)
40
+ chars = ('a'..'z').to_a - o[:letters][:ignore]
41
+ code_array = []
42
+ 1.upto(o[:letters][:count]) { code_array << chars[rand(chars.length)] }
43
+ code_array
44
+ end
45
+ end
46
+ end
Binary file
@@ -0,0 +1,24 @@
1
+ {
2
+ :count => 100, # number of captcha images to generate
3
+ :width => 90, # canvas width (px)
4
+ :height => 31, # canvas height (px)
5
+ :colors => {
6
+ :background => '#1B1C20',
7
+ :font => '#7385C5'
8
+ },
9
+ :letters => {
10
+ :baseline => 24,# text baseline (px)
11
+ :count => 6, # number of letters in captcha
12
+ :ignore => ['a','e','i','o','u','l','j','q'],
13
+ :points => 34, # font size (pts)
14
+ :width => 14, # width of a character (used to decrease or increase space between characters) (px)
15
+ },
16
+ :implode => 0.2, # http://www.imagemagick.org/RMagick/doc/image2.html#implode
17
+ :wave => { # http://www.imagemagick.org/RMagick/doc/image3.html#wave
18
+ :wavelength => (20..70), # range is used for randomness (px)
19
+ :amplitude => 3 # distance between peak and valley of sin wave (px)
20
+ },
21
+ :generate_on_startup => RAILS_ENV == 'production',
22
+ :destination => 'tmp/captchas',
23
+ :ttf => 'vendor/plugins/captcha/resources/captcha.ttf'
24
+ }
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe :captcha do
4
+ before(:each) do
5
+ @delay = 2 # 2 seconds
6
+ Captcha::Config.new(
7
+ :count => 10,
8
+ :destination => File.expand_path(File.dirname(__FILE__) + "/../tmp"),
9
+ :generate_every => @delay
10
+ )
11
+ FileUtils.rm_rf Captcha::Config.options[:destination]
12
+ @generator = Captcha::Generator.new
13
+ end
14
+ after(:all) do
15
+ FileUtils.rm_rf Captcha::Config.options[:destination]
16
+ end
17
+ it "should generate captchas" do
18
+ Captcha::Config.codes.length.should == 10
19
+ end
20
+ it "should only generate more captchas if the youngest file is older than the generate_every option" do
21
+ @generator.generate
22
+ Captcha::Config.codes.length.should == 10
23
+ sleep @delay
24
+ @generator.generate
25
+ Captcha::Config.codes.length.should == 20
26
+ end
27
+ it "should not allow more than twice the captcha limit to exist" do
28
+ sleep @delay
29
+ @generator.generate
30
+ Captcha::Config.codes.length.should == 20
31
+ sleep @delay
32
+ @generator.generate
33
+ Captcha::Config.codes.length.should == 20
34
+ end
35
+ it "should only destroy the oldest captchas" do
36
+ codes = Captcha::Config.codes
37
+ sleep @delay
38
+ @generator.generate
39
+ codes2 = Captcha::Config.codes
40
+ Captcha::Config.codes.length.should == 20
41
+ Captcha::Config.codes[9..-1].should_include_all_from(codes)
42
+ sleep @delay
43
+ @generator.generate
44
+ Captcha::Config.codes.length.should == 20
45
+ Captcha::Config.codes[9..-1].should_include_all_from(codes2)
46
+ Captcha::Config.codes.should_not_include_any_from(codes)
47
+ @generator.generate
48
+ Captcha::Config.codes.length.should == 20
49
+ Captcha::Config.codes.should_not_include_any_from(codes2)
50
+ end
51
+ class Array
52
+ def should_include_all_from(array)
53
+ included_all = true
54
+ array.each { |a| included_all = false unless self.include?(a) }
55
+ included_all
56
+ end
57
+ def should_not_include_any_from(array)
58
+ included_any = false
59
+ array.each { |a| included_any = true if self.include?(a) }
60
+ included_any
61
+ end
62
+ end
63
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,5 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'captcha'
5
+ require 'spec'
@@ -0,0 +1,55 @@
1
+ require File.expand_path('../lib/captcha/captcha.rb', File.dirname(__FILE__))
2
+
3
+ desc 'Updates config/captcha.rb'
4
+ task :captcha => 'captcha:config'
5
+
6
+ namespace :captcha do
7
+
8
+ desc 'Updates config/captcha.rb'
9
+ task :config => 'captcha:config:to_app'
10
+
11
+ desc 'Generates captchas'
12
+ task :generate do
13
+ Captchas.new
14
+ end
15
+
16
+ namespace :config do
17
+ desc 'Copies plugin resources to app'
18
+ task :to_app do
19
+ captcha_resource 'captchas.rb', 'config/captchas.rb'
20
+ end
21
+
22
+ desc 'Copies app resources to plugin'
23
+ task :to_plugin do
24
+ captcha_resource 'captchas.rb', 'config/captchas.rb', true
25
+ end
26
+
27
+ desc 'Removes plugin resources from app'
28
+ task :remove do
29
+ rm_rf 'config/captchas.rb'
30
+ end
31
+ end
32
+
33
+ def captcha_resource(type, to, reverse=false, overwrite=true)
34
+ from = "#{File.dirname(__FILE__)}/../resources/#{type}"
35
+ from, to = to, from if reverse
36
+ puts "=> Removing old #{type}..."
37
+ puts to
38
+ return if File.exists?(to) && !overwrite
39
+ if File.directory?(from)
40
+ FileUtils.remove_dir(to, true) if File.exists?(to)
41
+ FileUtils.mkdir_p to
42
+ else
43
+ File.unlink(to) if File.exists?(to)
44
+ end
45
+ puts "=> Copying #{type}..."
46
+ (File.directory?(from) ? Dir["#{from}/*"] : [from]).each do |f|
47
+ if File.directory? f
48
+ FileUtils.mkdir_p "#{to}/#{File.basename(f)}"
49
+ FileUtils.cp_r f, to
50
+ else
51
+ FileUtils.cp f, to
52
+ end
53
+ end if File.exists?(from)
54
+ end
55
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winton-captcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Winton Welsh
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-16 00:00:00 -07:00
12
+ date: 2009-03-23 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A simple captcha generator for Rails
16
+ description: An Rmagick based, Google-style captcha generator
17
17
  email: mail@wintoni.us
18
18
  executables: []
19
19
 
@@ -22,12 +22,24 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - captcha.gemspec
25
28
  - init.rb
26
29
  - lib/captcha.rb
27
- - lib/captcha
30
+ - lib/captcha/action.rb
28
31
  - lib/captcha/actions.rb
29
- - README.markdown
32
+ - lib/captcha/captcha.rb
33
+ - lib/captcha/config.rb
34
+ - lib/captcha/generator.rb
35
+ - lib/captcha/image.rb
36
+ - lib/captcha/routes.rb
30
37
  - resources/captcha.ttf
38
+ - resources/captchas.rb
39
+ - spec/lib/captcha_spec.rb
40
+ - spec/spec.opts
41
+ - spec/spec_helper.rb
42
+ - tasks/captcha.rake
31
43
  has_rdoc: false
32
44
  homepage: http://github.com/winton/captcha
33
45
  post_install_message:
@@ -53,6 +65,6 @@ rubyforge_project:
53
65
  rubygems_version: 1.2.0
54
66
  signing_key:
55
67
  specification_version: 2
56
- summary: A simple captcha generator for Rails
68
+ summary: An Rmagick based, Google-style captcha generator
57
69
  test_files: []
58
70
 
@@ -1,26 +0,0 @@
1
- module CaptchaActions
2
-
3
- def self.included(base)
4
- base.extend ClassMethods
5
- end
6
-
7
- module ClassMethods
8
- def acts_as_captcha
9
- include CaptchaActions::InstanceMethods
10
- end
11
- end
12
-
13
- module InstanceMethods
14
- def index
15
- new unless session[:captcha]
16
- send_file "#{RAILS_ROOT}/public/images/captchas/#{session[:captcha]}.jpg", :type => 'image/jpeg', :disposition => 'inline'
17
- end
18
-
19
- def new
20
- files = Dir["#{RAILS_ROOT}/public/images/captchas/*.jpg"]
21
- session[:captcha] = File.basename(files[rand(files.length)], '.jpg')
22
- index
23
- end
24
- end
25
-
26
- end