lib/flatulent.rb in flatulent-0.0.2 vs lib/flatulent.rb in flatulent-0.0.3
- old
+ new
@@ -1,22 +1,18 @@
-# TODO - encrypt time
-# TODO - noise is image chars
-# TODO - newline in value??
-# TODO - vertical offset in chars
-
class Flatulent
- Flatulent::VERSION = '0.0.2' unless defined? Flatulent::VERSION
+ Flatulent::VERSION = '0.0.3' unless defined? Flatulent::VERSION
def self.flatulent() Flatulent::VERSION end
def self.libdir() File.expand_path(__FILE__).gsub(%r/\.rb$/, '') end
require 'cgi'
require 'base64'
require 'digest/md5'
begin
require 'rubygems'
rescue LoadError
+ 42
end
begin
$:.unshift libdir
require 'text/figlet'
@@ -29,11 +25,10 @@
class Error < ::StandardError; end
class EncryptionError < Error; end
class TimeBombError < Error; end
-
singleton_class =
class << self
self
end
@@ -44,99 +39,108 @@
attribute('style'){ Hash[
'white-space' => 'pre',
'font-family' => 'monospace',
'font-weight' => 'bold',
'font-size' => 'medium',
- 'background' => '#ffc',
- 'color' => '#00f',
+ #'background' => '#ffc',
+ 'background' => '#ccffcc',
+ #'color' => '#000',
+ 'color' => '#330066',
'margin' => '2px',
'padding' => '2px',
'display' => 'table',
] }
- attribute('noise_style'){ Hash[
- 'color' => '#ccc',
- ] }
-
attribute('key'){ default_key }
- def valid? keywords = {}
+ def valid? keywords = {} #--{{{
begin
validate! keywords
true
rescue EncryptionError, TimeBombError
false
end
- end
+ end #--}}}
- def validate! keywords = {}
+ def validate! keywords = {} #--{{{
keywords = keywords['flatulent'] if keywords.has_key?('flatulent')
keywords = keywords[:flatulent] if keywords.has_key?(:flatulent)
opts = getopts keywords
captcha = opts['c'] or raise 'no captcha'
string = opts['s'] or raise 'no string'
time = opts['t'] or raise 'no time'
- expected = zeroh(decrypt(string))
- actual = zeroh(captcha)
+ expected = fuzzy(decrypt(string))
+ actual = fuzzy(captcha)
raise EncryptionError, "expected #{ expected } got #{ actual }" unless
expected == actual
timebomb = Time.at(Integer(decrypt(time))).utc rescue(raise("bad time #{ time }"))
raise TimeBombError unless Time.now.utc <= timebomb
return actual
- end
+ end #--}}}
- def zeroh string
- string.gsub(%r/[0Oo]/, '0') # ignore diffs between 0/o/O
- end
+# 0==o==O (zero and oh's)
+# l==l (one and el)
+# 2==z==Z (two and z's)
+# 5==s==S (5 and s's)
+ def fuzzy string #--{{{
+ result = string.dup
+ close_enough = {
+ '0OoQ' => '0',
+ '1l' => '1',
+ '2zZ' => '2',
+ '5sS' => '5',
+ }
+ #string.gsub(%r/[0Oo]/, '0') # ignore diffs between 0/o/O
+ close_enough.each do |chars,char|
+ result.gsub! %r"[#{ chars }]", char
+ end
+ result
+ end #--}}}
- def blowfish
+ def blowfish #--{{{
@blowfish ||= Hash.new{|h,k| h[k] = Crypt::Blowfish.new(key)}
- end
+ end #--}}}
- def munge string
- string.strip.downcase
- end
+ def munge string #--{{{
+ string.strip #.downcase
+ end #--}}}
- def encrypt string
+ def encrypt string #--{{{
Base64.encode64(blowfish[key].encrypt_string(string.to_s)).chop # kill "\n"
- end
+ end #--}}}
- def decrypt string
+ def decrypt string #--{{{
munge(blowfish[key].decrypt_string(Base64.decode64("#{ string }\n")))
- end
+ end #--}}}
- def getopts options
+ def getopts options #--{{{
lambda do |key, *default|
default = default.first
break options[key] if options.has_key?(key)
key = key.to_s
break options[key] if options.has_key?(key)
key = key.to_sym
break options[key] if options.has_key?(key)
break default
end
- end
+ end #--}}}
- def default_key
- #attribute('default_key') do
+ def default_key #--{{{
return @default_key if defined? @default_key
require 'socket'
hostname = Socket.gethostname
maddr = mac_address rescue nil
warn "could not determine mac addresss!" unless maddr
- #puts(( Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--" ))
- #Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--"
- #@default_key = "--#{ hostname }--#{ maddr }--"
@default_key = "--#{ hostname }--#{ maddr }--"
- end
+ end #--}}}
- def mac_address
+ def mac_address #--{{{
return @mac_address if defined? @mac_address
re = %r/[^:\-](?:[0-9A-F][0-9A-F][:\-]){5}[0-9A-F][0-9A-F][^:\-]/io
cmds = '/sbin/ifconfig', '/bin/ifconfig', 'ifconfig', 'ipconfig /all'
null = test(?e, '/dev/null') ? '/dev/null' : 'NUL'
@@ -158,32 +162,37 @@
maddr.strip!
maddr.instance_eval{ @list = candidates; def list() @list end }
@mac_address = maddr
- end
+ end #--}}}
- def figlet options = {}
+ def figlet options = {} #--{{{
new(options).figlet
- end
- def figlets options = {}
+ end #--}}}
+
+ def figlets options = {} #--{{{
new(options).figlets
- end
- def element options = {}
+ end #--}}}
+
+ def element options = {} #--{{{
new(options).element
- end
- def form_tags options = {}
+ end #--}}}
+
+ def form_tags options = {} #--{{{
new(options).form_tags
- end
- def form options = {}
+ end #--}}}
+
+ def form options = {} #--{{{
new(options).form
- end
+ end #--}}}
- def latest_prototype_lib
+ def latest_prototype_lib #--{{{
'http://www.prototypejs.org/assets/2007/6/20/prototype.js'
- end
- def require_prototype
+ end #--}}}
+
+ def require_prototype #--{{{
%Q`
<script type="text/javascript">
var prototype = true;
try{ Prototype; } catch(e) { prototype = false };
if(!prototype){
@@ -202,28 +211,30 @@
js.src='#{ latest_prototype_lib }';
document.getElementsByTagName('head')[0].appendChild(js);
}
</script>
`
- end
- def javascript options = {}
+ end #--}}}
+
+ def javascript options = {} #--{{{
id = options[:id] || options['id'] || 'flatulent'
url = options[:url] || options['url'] || '/flatulent/captcha'
%Q`
#{ require_prototype }
<script type="text/javascript">
new Ajax.Updater('#{ id }', '#{ url }', { method: 'get' });
</script>
`
- end
- def ajax options = {}
+ end #--}}}
+
+ def ajax options = {} #--{{{
id = options[:id] || options['id'] || 'flatulent'
%Q`
<div id="#{ id }"></div>
#{ Flatulent.javascript }
`
- end
+ end #--}}}
end
singleton_class.attributes.each{|a| attribute(a){ self.class.send a}}
attribute 'string'
@@ -233,16 +244,18 @@
attribute 'id'
attribute 'action'
attribute 'ttl'
attribute 'figlet'
+ attribute 'vapour'
+ attribute 'captcha'
attribute 'figlets'
attribute 'element'
attribute 'form_tags'
attribute 'form'
- def initialize arg = {}
+ def initialize arg = {} #--{{{
if Hash === arg
opt = getopts arg
@size = Integer opt[ 'size', 4 ]
@string = String opt[ 'string', generate_random_string ]
else
@@ -250,144 +263,280 @@
@string = String arg
@size = @string.size
end
@font = String opt[ 'font', 'big' ]
- @noise = Float opt[ 'noise', 0.04 ]
+ @noise = Float opt[ 'noise', 0.22 ]
@id = String opt[ 'id', 'flatulent' ]
@action = String opt[ 'action' ]
- @ttl = Integer opt[ 'ttl', 256 ]
+ @ttl = Integer opt[ 'ttl', 300 ]
+ @horizontal_fudge_factor = Integer opt[ 'horizontal_fudge_factor', 4 ]
+ @vertical_fudge_factor = Integer opt[ 'vertical_fudge_factor', 4 ]
+
+ @vapour_chars = Integer opt[ 'vapour_chars', 5 * @size ]
+ @vapour_level = Float opt[ 'vapour_level', 0.77 ]
figlet!
+ vapour!
+ captcha!
element!
form_tags!
form!
- end
+ end #--}}}
- def generate_random_string
- chars = ('A' .. 'Z').to_a + ('1' .. '9').to_a ### zero is too much like o/O
- Array.new(@size).map{ chars[rand(chars.size - 1)]}.join
- end
+ def random_charset #--{{{
+ return @random_charset if defined? @random_charset
+ @random_charset = ('A' .. 'Z').to_a + ('1' .. '9').to_a
+ end #--}}}
- def figlet!
- spaced = @string.split(%r//).join #.join(' ')
+ def generate_random_string #--{{{
+ chars = []
+ n = random_charset.size - 1
+ loop {
+ ( chars << random_charset[rand(n)] ).uniq!
+ break if chars.size >= @size or random_charset.size == chars.size
+ }
+ chars
+ end #--}}}
+
+ def random_char #--{{{
+ n = random_charset.size - 1
+ random_charset[rand(n)]
+ end #--}}}
+
+ def figlet! #--{{{
+ #spaced = @string.split(%r//).join #' ' #.join(' ')
fontfile = File.join fontdir, "#{ @font }.flf"
font = Text::Figlet::Font.new fontfile
typesetter = Text::Figlet::Typesetter.new font
+
@figlets = []
- chars = spaced.split %r//
- chars.each{|char| @figlets << typesetter[char]}
- @figlet = typesetter[spaced]
- end
+ chars = @string.split %r//
+ #chars.each{|char| @figlets << typesetter[char]}
- def element!
+ # horz fudge
+ chars.each do |char|
+ figlet = typesetter[char]
+ rows = figlet.split %r/\n/
+ height = rows.size
+ offset_l = " " * rand(@horizontal_fudge_factor)
+ offset_r = " " * rand(@horizontal_fudge_factor)
+ rows.size.times do |i|
+ rows[i] = "#{ offset_l }#{ rows[i] }#{ offset_r }"
+ end
+ @figlets.push rows
+ end
+
+ # vert fudge
+ @figlets.map! do |rows|
+ width = rows.first.size
+ offset_t = Array.new(rand(@vertical_fudge_factor)).map{ " " * width }
+ offset_b = Array.new(rand(@vertical_fudge_factor)).map{ " " * width }
+ offset_t + rows + offset_b
+ end
+
+ # vert normalize vert
+ tallest = @figlets.map{|rows| rows.size}.max
+ @figlets.size.times do |i|
+ rows = @figlets[i]
+ unless rows.size == tallest
+ width = rows.first.size
+ until rows.size == tallest
+ blank = " " * width
+ rows << blank
+ end
+ end
+ end
+
+ # generate final grid
+ grid = []
+ grid << (gridrow = '')
+ catch :done do
+ loop do
+ @figlets.each do |rows|
+ row = rows.shift || throw(:done)
+ gridrow << row
+ end
+ #gridrow << "\n"
+ grid << (gridrow = '')
+ end
+ end
+
+ # trim top and bottom iff emtpy
+=begin
+ empty = lambda{|row| not rnow.detect{|cell| cell !~ %r"br|nbsp"}}
+ grid.delete_if{|row| row.strip.empty?}
+=end
+
+ @figlet_width = grid.first.size
+ @figlet_height = grid.size
+ @figlet = grid.join("\n")
+ end #--}}}
+
+ def vapour! #--{{{
+ #spaced = @string.split(%r//).join #' ' #.join(' ')
+ fontfile = File.join fontdir, "#{ @font }.flf"
+ font = Text::Figlet::Font.new fontfile
+ typesetter = Text::Figlet::Typesetter.new font
+
+ grid = Array.new(@figlet_height).map{ Array.new(@figlet_width){ " " } }
+ width = grid.first.size
+ height = grid.size
+
+ @vapour_chars.times do
+ figlet = typesetter[random_char]
+
+ idxs = []
+ space = " "[0]
+ newline = "\n"[0]
+ i = 0
+ figlet.each_byte do |byte|
+ idxs << i unless byte == space or byte == newline
+ i += 1
+ end
+#p idxs
+ to_vapourize = idxs.sort_by{ rand }.first((idxs.size * @vapour_level).ceil)
+#p to_vapourize
+ to_vapourize.each{|idx| figlet[idx] = " "}
+#puts figlet
+
+ figlet_grid = [] and figlet.each_line do |line|
+ figlet_grid << line.chomp.split(%r"")
+ end
+
+ xoff = rand(width - 1)
+ yoff = rand(height - 1)
+
+ figlet_grid.each_with_index do |row, y|
+ row.each_with_index do |char, x|
+ j = y + yoff
+ i = x + xoff
+ next if j >= height or i >= width
+ next if char == " "
+ grid[ y + yoff ][ x + xoff ] = char
+ end
+ end
+ end
+
+ @vapour = grid.map{|row| row.join}.join("\n")
+ end #--}}}
+
+ def captcha! #--{{{
+ @captcha = " " * @figlet.size
+ space = " "[0]
+ newline = "\n"[0]
+ noisy = %w`| / - _ ( ) \\ ! [ ]`
+
+ @figlet.size.times do |i|
+ fbyte = @figlet[i]
+ vbyte = @vapour[i]
+
+ if fbyte == newline
+ @captcha[i] = newline
+ elsif fbyte == space
+ #if vbyte == space
+ #@captcha[i] = noisy[ rand(noisy.size) ]
+ #else
+ @captcha[i] = vbyte
+ #end
+ else
+ @captcha[i] = fbyte
+ end
+ end
+
+
+=begin
+ alpha = ('A'..'Z').to_a + ('a'..'z').to_a
+ transform = {
+ '|' => rchar(%w'. * \` + , ; : '),
+ '/' => rchar(%w'\\ ] [ { } ( ) @' + alpha),
+ '\\' => rchar(%w'/ ] [ { } ( ) @' + alpha),
+ '-' => rchar(%w'_ * ^ # @ ~'),
+ '_' => rchar(%w'- * ^ # @ ~'),
+ '(' => rchar(%w'\\ / ] [ { } ) @' + alpha),
+ ')' => rchar(%w'\\ / ] [ { } ( @' + alpha),
+ }
+
+ (@noise * @captcha.size).ceil.times do |i|
+ cbyte = @captcha[i]
+ if cell =~ %r"nbsp;"
+ end
+=end
+ @captcha
+ end #--}}}
+
+ def element! #--{{{
rows = []
rows << (row = [])
- chars = @figlet.split %r//
+ chars = @captcha.split %r//
size = chars.size
last = size - 1
chars.each_with_index do |char, idx|
content =
case char
when %r/\n/o
"<br>"
when %r/\s/o
- #rand > 0.42 ? " " : " "
" "
when %r/([^\s])/o
CGI.escapeHTML $1
end
- Array.new(rand(42)){ content = "<span>#{ content }</span>"}
+ Array.new(rand(10)){ content = "<span>#{ content }</span>"}
row << content
rows << (row = []) unless idx == last
end
- noisy = %W` | / - _ ( ) \ `
+
+=begin
+ noisy = %w`| / - _ ( ) \\`
+ alpha = ('A'..'Z').to_a + ('a'..'z').to_a
+ transform = {
+ '|' => rchar(%w'. * \` + , ; : '),
+ '/' => rchar(%w'\\ ] [ { } ( ) @' + alpha),
+ '\\' => rchar(%w'/ ] [ { } ( ) @' + alpha),
+ '-' => rchar(%w'_ * ^ # @ ~'),
+ '_' => rchar(%w'- * ^ # @ ~'),
+ '(' => rchar(%w'\\ / ] [ { } ) @' + alpha),
+ ')' => rchar(%w'\\ / ] [ { } ( @' + alpha),
+ }
+
(@noise * chars.size).ceil.times do
y = rand(rows.size - 1)
x = rand(rows.first.size - 1)
- next if rows[y][x] =~ %r"br"
- char = noisy[ rand(noisy.size) ]
- rows[y][x] = char
+ cell = rows[y][x]
+ next if cell =~ %r"br"
+ char =
+ if cell =~ %r"nbsp;"
+ noisy[ rand(noisy.size) ]
+ else
+ #(transform[cell] || lambda{'.'}).call
+ nil
+ end
+ rows[y][x] = char if char
end
+=end
content = rows.join
@element = "<pre id='#{ @id }_element' style='#{ css }'>#{ content }</pre>"
- end
+ end #--}}}
-=begin
+ def rchar *list #--{{{
+ list = list.flatten
+ n = list.size - 1
+ lambda{list[ rand(n) ]}
+ end #--}}}
- def element!
- cells = []
-
- @figlets.each do |figlet|
- rows = []
- rows << (row = [])
-
- offset_t = Array.new(rand(4)).map{ "\n"}
- offset_b = Array.new(rand(4)).map{ "\n"}
-
- offset_l = Array.new(rand(4)).map{ " "}
- offset_r = Array.new(rand(4)).map{ " "}
-
- chars = offset_t + figlet.split(%r//) + offset_b
- size = chars.size
- last = size - 1
- #drawn = chars.select{|char| char !~ %r/\s/}
- drawn = %w` | / \ < > v ^ - _ ( ) `
-
- chars.each_with_index do |char, idx|
- content =
- case char
- when %r/\n/o
- "<br>"
- when %r/\s/o
- #rand > 0.42 ? " " : " "
- " "
- when %r/([^\s])/o
- CGI.escapeHTML $1
- end
- #rand(10).times{ content = "<span>#{ content }</span>" }
- row << content
- rows << (row = []) unless idx == last
- end
-
- noisy = %w` | / \ - _ ( ) `
- (@noise * chars.size).ceil.times do
- y = rand(rows.size - 1)
- x = rand(rows.first.size - 1)
- next if rows[y][x] == "<br>"
- char = noisy[ rand(noisy.size) ]
- rows[y][x] = char
- end
-
- content = rows.join
- cells << content
- end
-
- formatted = lambda{|x| "<pre class='#{ @id }_figlet' style='#{ css }'>#{ x }</pre>"}
-
- @element =
- "<table id='#{ @id }_element' border='0' cellpadding='0' cellspacing='0' bgcolor='#{ style["background"] }'><tr>" <<
- cells.map{|cell| "<td>#{ formatted[cell] }</td>"}.join <<
- "</tr></table>"
- end
-=end
-
- def css
+ def css #--{{{
css_for style
- end
- def noise_css
- css_for noise_style
- end
+ end #--}}}
- def css_for hash
+ def css_for hash #--{{{
hash.map{|kv| kv.join ':' }.join ';'
- end
+ end #--}}}
- def form_tags!
+ def form_tags! #--{{{
n = @string.scan(%r/\w/).size
string = @string
timebomb = Time.now.utc.to_i + @ttl
@form_tags = <<-html
#{ element }
@@ -396,46 +545,46 @@
</p>
<input type='textarea' name='#{ @id }[c]' id='#{ @id }_textarea' />
<input type='hidden' name='#{ @id }[s]' id='#{ @id }_e' value='#{ encrypt string }' />
<input type='hidden' name='#{ @id }[t]' id='#{ @id }_v' value='#{ encrypt timebomb }' />
html
- end
+ end #--}}}
alias_method 'to_html', 'form_tags'
- def encrypt string
+ def encrypt string #--{{{
self.class.encrypt string.to_s
- end
+ end #--}}}
- def encrypted
+ def encrypted #--{{{
self.class.encrypt @string
- end
+ end #--}}}
- def munge string
+ def munge string #--{{{
self.class.munge string
- end
+ end #--}}}
- def form!
+ def form! #--{{{
action = "action='#{ @action }'"
@form = <<-html
<form method='post' #{ action }>
#{ form_tags }
<input type='submit' name='#{ @id }[submit]' id='#{ @id }_submit' value='Submit' />
</form>
html
- end
+ end #--}}}
- def to_html
+ def to_html #--{{{
element
- end
+ end #--}}}
- def to_s
+ def to_s #--{{{
form
- end
+ end #--}}}
- def getopts options
+ def getopts options #--{{{
self.class.getopts options
- end
+ end #--}}}
end
def Flatulent(*a, &b) Flatulent.new(*a, &b) end
if $0 == __FILE__
@@ -444,7 +593,12 @@
#Flatulent.validate! :t => string, :e => Flatulent.encrypt(string), :v => (Time.now.utc.to_i + 60)
#e = Flatulent.encrypt('foobar')
#p e
#p Flatulent.decrypt(e)
- puts Flatulent.figlet('foobar')
+ f = Flatulent.new #('foobar')
+ puts f.figlet
+ puts('-' * 79)
+ puts f.vapour
+ puts('-' * 79)
+ puts f.captcha
end