captcha 1.2.1
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.
- data/MIT-LICENSE +20 -0
- data/README.markdown +89 -0
- data/Rakefile +32 -0
- data/gemspec.rb +21 -0
- data/init.rb +7 -0
- data/lib/captcha.rb +5 -0
- data/lib/captcha/action.rb +34 -0
- data/lib/captcha/cipher.rb +40 -0
- data/lib/captcha/config.rb +83 -0
- data/lib/captcha/generator.rb +26 -0
- data/lib/captcha/image.rb +47 -0
- data/lib/captcha/model.rb +56 -0
- data/resources/captcha.ttf +0 -0
- data/spec/lib/captcha_spec.rb +37 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +5 -0
- data/tasks/captcha.rake +6 -0
- metadata +80 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Winton Welsh
|
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.
|
data/README.markdown
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
captcha
|
2
|
+
=======
|
3
|
+
|
4
|
+
A Google-style captcha for enterprise Rails apps
|
5
|
+
|
6
|
+
Goals
|
7
|
+
-----
|
8
|
+
|
9
|
+
* Batch generate captchas
|
10
|
+
* Use ciphered filenames (no need to store filename/captcha pairs)
|
11
|
+
* Easy configuration
|
12
|
+
* Number of captchas
|
13
|
+
* Period for captcha refresh
|
14
|
+
* Colors, wave, implode
|
15
|
+
* Handle lots of users
|
16
|
+
|
17
|
+
Install
|
18
|
+
-------
|
19
|
+
|
20
|
+
script/plugin install git://github.com/winton/captcha.git
|
21
|
+
|
22
|
+
### Create lib/captcha_config.rb (optional)
|
23
|
+
|
24
|
+
<pre>
|
25
|
+
Captcha::Config.new(
|
26
|
+
# Used for filename cipher
|
27
|
+
:password => 'something-unique',
|
28
|
+
# Captcha colors
|
29
|
+
:colors => {
|
30
|
+
:background => '#FFFFFF',
|
31
|
+
:font => '#080288'
|
32
|
+
},
|
33
|
+
# Number of captcha images to generate
|
34
|
+
:count => RAILS_ENV == 'production' ? 500 : 10,
|
35
|
+
# Where to write captchas
|
36
|
+
:destination => "#{RAILS_ROOT}/public/images/captchas",
|
37
|
+
# Generate new batch every day
|
38
|
+
:generate_every => 24 * 60 * 60
|
39
|
+
)
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
See <code>lib/captcha/config.rb</code> for more options.
|
43
|
+
|
44
|
+
### application_controller.rb
|
45
|
+
|
46
|
+
<pre>
|
47
|
+
class ApplicationController < ActionController::Base
|
48
|
+
acts_as_captcha
|
49
|
+
end
|
50
|
+
</pre>
|
51
|
+
|
52
|
+
You may now use the <code>reset_captcha</code> method in any controller.
|
53
|
+
|
54
|
+
### user.rb
|
55
|
+
|
56
|
+
<pre>
|
57
|
+
class User < ActiveRecord::Base
|
58
|
+
acts_as_captcha :base => "base error when captcha fails", :field => "field error when captcha fails"
|
59
|
+
end
|
60
|
+
</pre>
|
61
|
+
|
62
|
+
With no parameters, a default error is added to the "captcha" field (<code>:field => true</code>).
|
63
|
+
|
64
|
+
Specify <code>:base => true</code> to use a default error for base.
|
65
|
+
|
66
|
+
### In your view
|
67
|
+
|
68
|
+
<pre>
|
69
|
+
<img src="/images/captchas/<%= session[:captcha] %>.jpg" />
|
70
|
+
<%= text_field_tag(:captcha) %>
|
71
|
+
</pre>
|
72
|
+
|
73
|
+
### In your controller
|
74
|
+
|
75
|
+
<pre>
|
76
|
+
user = User.new
|
77
|
+
user.known_captcha = session[:captcha]
|
78
|
+
user.captcha = params[:captcha]
|
79
|
+
user.save
|
80
|
+
reset_captcha
|
81
|
+
</pre>
|
82
|
+
|
83
|
+
### crontab
|
84
|
+
|
85
|
+
<pre>
|
86
|
+
0 0 * * * cd /path/to/rails/app && /usr/bin/rake RAILS_ENV=production captcha:generate
|
87
|
+
</pre>
|
88
|
+
|
89
|
+
Your config file sets the captcha refresh period. The rake task just checks if its time to repopulate, and does so if necessary.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'gemspec'
|
6
|
+
|
7
|
+
desc "Generate gemspec"
|
8
|
+
task :gemspec do
|
9
|
+
File.open("#{Dir.pwd}/#{GEM_NAME}.gemspec", 'w') do |f|
|
10
|
+
f.write(GEM_SPEC.to_ruby)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Install gem"
|
15
|
+
task :install do
|
16
|
+
Rake::Task['gem'].invoke
|
17
|
+
`sudo gem uninstall #{GEM_NAME} -x`
|
18
|
+
`sudo gem install pkg/#{GEM_NAME}*.gem`
|
19
|
+
`rm -Rf pkg`
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Package gem"
|
23
|
+
Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
|
24
|
+
pkg.gem_spec = GEM_SPEC
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Run specs"
|
28
|
+
Spec::Rake::SpecTask.new do |t|
|
29
|
+
t.rcov = true
|
30
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
31
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
32
|
+
end
|
data/gemspec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
GEM_NAME = 'captcha'
|
2
|
+
GEM_FILES = FileList['**/*'] - FileList[
|
3
|
+
'coverage', 'coverage/**/*',
|
4
|
+
'pkg', 'pkg/**/*'
|
5
|
+
]
|
6
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
7
|
+
# == CONFIGURE ==
|
8
|
+
s.author = "Winton Welsh"
|
9
|
+
s.email = "mail@wintoni.us"
|
10
|
+
s.homepage = "http://github.com/winton/#{GEM_NAME}"
|
11
|
+
s.summary = "A Google-style captcha for enterprise Rails apps"
|
12
|
+
# == CONFIGURE ==
|
13
|
+
s.add_dependency('rmagick', '>=2.9.2')
|
14
|
+
s.extra_rdoc_files = [ "README.markdown" ]
|
15
|
+
s.files = GEM_FILES.to_a
|
16
|
+
s.has_rdoc = false
|
17
|
+
s.name = GEM_NAME
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.require_path = "lib"
|
20
|
+
s.version = "1.2.1"
|
21
|
+
end
|
data/init.rb
ADDED
data/lib/captcha.rb
ADDED
@@ -0,0 +1,5 @@
|
|
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/cipher.rb"
|
5
|
+
require File.dirname(__FILE__) + "/captcha/generator.rb"
|
@@ -0,0 +1,34 @@
|
|
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
|
+
unless included_modules.include? InstanceMethods
|
11
|
+
include InstanceMethods
|
12
|
+
end
|
13
|
+
before_filter :assign_captcha
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
private
|
19
|
+
|
20
|
+
def assign_captcha
|
21
|
+
unless session[:captcha] && Captcha::Config.exists?(session[:captcha])
|
22
|
+
files = Captcha::Config.captchas
|
23
|
+
session[:captcha] = File.basename(files[rand(files.length)], '.jpg')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_captcha
|
28
|
+
session[:captcha] = nil
|
29
|
+
assign_captcha
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Captcha
|
5
|
+
class Cipher
|
6
|
+
@@key = Digest::SHA1.hexdigest(Config.options[:password])
|
7
|
+
@@iv = 'captchas'*2
|
8
|
+
|
9
|
+
def self.encrypt(text)
|
10
|
+
# Encrypt
|
11
|
+
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
12
|
+
cipher.encrypt
|
13
|
+
cipher.key = @@key
|
14
|
+
cipher.iv = @@iv
|
15
|
+
encrypted = cipher.update(text)
|
16
|
+
encrypted << cipher.final
|
17
|
+
# Turn into chr codes separated by underscores
|
18
|
+
# 135_14_163_53_43_135_172_31_1_23_169_81_49_110_49_230
|
19
|
+
encrypted = (0..encrypted.length-1).collect do |x|
|
20
|
+
encrypted[x]
|
21
|
+
end
|
22
|
+
encrypted.join('_')
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.decrypt(text)
|
26
|
+
# Decode chr coded string
|
27
|
+
encrypted = text.split('_').collect do |x|
|
28
|
+
x.to_i.chr
|
29
|
+
end
|
30
|
+
encrypted = encrypted.join('')
|
31
|
+
# Decrypt
|
32
|
+
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
|
33
|
+
cipher.decrypt
|
34
|
+
cipher.key = @@key
|
35
|
+
cipher.iv = @@iv
|
36
|
+
decrypted = cipher.update(encrypted)
|
37
|
+
decrypted << cipher.final
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Captcha
|
2
|
+
class Config
|
3
|
+
|
4
|
+
if defined?(RAILS_ENV)
|
5
|
+
PRODUCTION = RAILS_ENV == 'production' || RAILS_ENV == 'staging'
|
6
|
+
ROOT = "#{RAILS_ROOT}/"
|
7
|
+
else
|
8
|
+
PRODUCTION = false
|
9
|
+
ROOT = ""
|
10
|
+
end
|
11
|
+
ONE_DAY = 24 * 60 * 60
|
12
|
+
|
13
|
+
@@options = {
|
14
|
+
:password => 'captcha',
|
15
|
+
:colors => {
|
16
|
+
:background => '#FFFFFF',
|
17
|
+
:font => '#080288'
|
18
|
+
},
|
19
|
+
# number of captcha images to generate
|
20
|
+
:count => PRODUCTION ? 500 : 10,
|
21
|
+
:destination => "#{ROOT}public/images/captchas",
|
22
|
+
:dimensions => {
|
23
|
+
# canvas height (px)
|
24
|
+
:height => 32,
|
25
|
+
# canvas width (px)
|
26
|
+
:width => 110
|
27
|
+
},
|
28
|
+
:generate_every => PRODUCTION ? ONE_DAY * 1 : ONE_DAY * 10000,
|
29
|
+
# http://www.imagemagick.org/RMagick/doc/image2.html#implode
|
30
|
+
:implode => 0.2,
|
31
|
+
:letters => {
|
32
|
+
# text baseline (px)
|
33
|
+
:baseline => 25,
|
34
|
+
# number of letters in captcha
|
35
|
+
:count => 6,
|
36
|
+
:ignore => ['a','e','i','o','u','l','j','q','v'],
|
37
|
+
# font size (pts)
|
38
|
+
:points => 38,
|
39
|
+
# width of a character (used to decrease or increase space between characters) (px)
|
40
|
+
:width => 17
|
41
|
+
},
|
42
|
+
:ttf => File.expand_path("#{File.dirname(__FILE__)}/../../resources/captcha.ttf"),
|
43
|
+
# http://www.imagemagick.org/RMagick/doc/image3.html#wave
|
44
|
+
:wave => {
|
45
|
+
# range is used for randomness (px)
|
46
|
+
:wavelength => (40..70),
|
47
|
+
# distance between peak and valley of sin wave (px)
|
48
|
+
:amplitude => 3
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
def initialize(options={})
|
53
|
+
@@options.merge!(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.captchas
|
57
|
+
Dir["#{@@options[:destination]}/*.jpg"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.codes
|
61
|
+
self.captchas.collect do |f|
|
62
|
+
File.basename f, '.jpg'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.exists?(code)
|
67
|
+
File.exists?("#{@@options[:destination]}/#{code}.jpg")
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.options
|
71
|
+
@@options
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.last_modified
|
75
|
+
file = self.captchas.first
|
76
|
+
if file && File.exists?(file)
|
77
|
+
File.mtime(file)
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
path = Config.options[:destination]
|
11
|
+
Config.captchas.each do |captcha|
|
12
|
+
FileUtils.rm_f captcha
|
13
|
+
end
|
14
|
+
FileUtils.mkdir_p path
|
15
|
+
(1..Config.options[:count]).each do |x|
|
16
|
+
image = Image.new Config.options
|
17
|
+
path = "#{Config.options[:destination]}/#{Cipher.encrypt(image.code)}.jpg"
|
18
|
+
next if File.exists?(path)
|
19
|
+
File.open(path, 'w') do |f|
|
20
|
+
f << image.data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
GC.start
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
|
3
|
+
module Captcha
|
4
|
+
class Image
|
5
|
+
|
6
|
+
include Magick
|
7
|
+
attr_reader :code, :data
|
8
|
+
|
9
|
+
def initialize(o)
|
10
|
+
generate_code o
|
11
|
+
|
12
|
+
canvas = Magick::Image.new(o[:dimensions][:width], o[:dimensions][:height]) {
|
13
|
+
self.background_color = o[:colors][:background]
|
14
|
+
}
|
15
|
+
|
16
|
+
text = Magick::Draw.new
|
17
|
+
text.font = File.expand_path o[:ttf]
|
18
|
+
text.pointsize = o[:letters][:points]
|
19
|
+
|
20
|
+
cur = 0
|
21
|
+
@code.each { |c|
|
22
|
+
text.annotate(canvas, 0, 0, cur, o[:letters][:baseline], c) {
|
23
|
+
self.fill = o[:colors][:font]
|
24
|
+
}
|
25
|
+
cur += o[:letters][:width]
|
26
|
+
}
|
27
|
+
|
28
|
+
w = o[:wave][:wavelength]
|
29
|
+
canvas = canvas.wave(o[:wave][:amplitude], rand(w.last - w.first) + w.first)
|
30
|
+
canvas = canvas.implode(o[:implode])
|
31
|
+
|
32
|
+
@code = @code.to_s
|
33
|
+
@data = canvas.to_blob { self.format = "JPG" }
|
34
|
+
canvas.destroy!
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def generate_code(o)
|
40
|
+
chars = ('a'..'z').to_a - o[:letters][:ignore]
|
41
|
+
@code = []
|
42
|
+
1.upto(o[:letters][:count]) do
|
43
|
+
@code << chars[rand(chars.length)]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Captcha
|
2
|
+
module Model
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ActMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActMethods
|
8
|
+
def acts_as_captcha(options={})
|
9
|
+
extend ClassMethods
|
10
|
+
include InstanceMethods
|
11
|
+
attr_reader :captcha, :known_captcha
|
12
|
+
cattr_accessor :captcha_options
|
13
|
+
self.captcha_options = options
|
14
|
+
validate :captcha_must_match_known_captcha
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def captcha=(c)
|
23
|
+
@captcha = c || ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def known_captcha=(c)
|
27
|
+
@known_captcha = c || ''
|
28
|
+
end
|
29
|
+
|
30
|
+
def captcha_must_match_known_captcha
|
31
|
+
return true if self.captcha.nil? || self.known_captcha.nil?
|
32
|
+
if self.captcha.strip.downcase != Captcha::Cipher.decrypt(self.known_captcha)
|
33
|
+
if self.captcha_options[:base]
|
34
|
+
self.errors.add_to_base(
|
35
|
+
case self.captcha_options[:base]
|
36
|
+
when true
|
37
|
+
"Enter the correct text in the image (6 characters)"
|
38
|
+
else
|
39
|
+
self.captcha_options[:base]
|
40
|
+
end
|
41
|
+
)
|
42
|
+
else
|
43
|
+
self.errors.add(:captcha,
|
44
|
+
case self.captcha_options[:field]
|
45
|
+
when true, nil
|
46
|
+
"text does not match the text in the image."
|
47
|
+
else
|
48
|
+
self.captcha_options[:field]
|
49
|
+
end
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
Binary file
|
@@ -0,0 +1,37 @@
|
|
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 generate fresh captchas if the files are older than the generate_every option" do
|
21
|
+
codes = Captcha::Config.codes
|
22
|
+
sleep @delay
|
23
|
+
@generator.generate
|
24
|
+
codes.should_not == Captcha::Config.codes
|
25
|
+
end
|
26
|
+
it "should not regenerate before the files are older than the generate_every option" do
|
27
|
+
codes = Captcha::Config.codes
|
28
|
+
sleep 1
|
29
|
+
@generator.generate
|
30
|
+
codes.should == Captcha::Config.codes
|
31
|
+
end
|
32
|
+
it "should not allow more than the captcha limit to exist" do
|
33
|
+
sleep @delay
|
34
|
+
@generator.generate
|
35
|
+
Captcha::Config.codes.length.should == 10
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
data/tasks/captcha.rake
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: captcha
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Winton Welsh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-06 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rmagick
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.9.2
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: mail@wintoni.us
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.markdown
|
33
|
+
files:
|
34
|
+
- gemspec.rb
|
35
|
+
- init.rb
|
36
|
+
- lib/captcha/action.rb
|
37
|
+
- lib/captcha/cipher.rb
|
38
|
+
- lib/captcha/config.rb
|
39
|
+
- lib/captcha/generator.rb
|
40
|
+
- lib/captcha/image.rb
|
41
|
+
- lib/captcha/model.rb
|
42
|
+
- lib/captcha.rb
|
43
|
+
- MIT-LICENSE
|
44
|
+
- Rakefile
|
45
|
+
- README.markdown
|
46
|
+
- resources/captcha.ttf
|
47
|
+
- spec/lib/captcha_spec.rb
|
48
|
+
- spec/spec.opts
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- tasks/captcha.rake
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/winton/captcha
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A Google-style captcha for enterprise Rails apps
|
79
|
+
test_files: []
|
80
|
+
|