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 ? "&nbsp;" : " " "&nbsp;" 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` | / \ &lt; &gt; v ^ - _ ( ) ` - - chars.each_with_index do |char, idx| - content = - case char - when %r/\n/o - "<br>" - when %r/\s/o - #rand > 0.42 ? "&nbsp;" : " " - "&nbsp;" - 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