flatulent 0.0.1 → 0.0.2
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/README +60 -16
- data/flatulent-0.0.2.gem +0 -0
- data/lib/flatulent/crypt/blowfish-tables.rb +190 -0
- data/lib/flatulent/crypt/blowfish.rb +109 -0
- data/lib/flatulent/crypt/cbc.rb +123 -0
- data/lib/flatulent/crypt/gost.rb +140 -0
- data/lib/flatulent/crypt/idea.rb +193 -0
- data/lib/flatulent/crypt/noise.rb +94 -0
- data/lib/flatulent/crypt/purerubystringio.rb +378 -0
- data/lib/flatulent/crypt/rijndael-tables.rb +117 -0
- data/lib/flatulent/crypt/rijndael.rb +269 -0
- data/lib/flatulent/crypt/stringxor.rb +27 -0
- data/lib/flatulent.rb +332 -121
- data/lib/flatulent.rb.bak +337 -0
- data/rails/app/controllers/flatulent_controller.rb +61 -6
- data/rails/lib/flatulent/attributes.rb +79 -0
- data/rails/lib/flatulent/crypt/blowfish-tables.rb +190 -0
- data/rails/lib/flatulent/crypt/blowfish.rb +109 -0
- data/rails/lib/flatulent/crypt/cbc.rb +123 -0
- data/rails/lib/flatulent/crypt/gost.rb +140 -0
- data/rails/lib/flatulent/crypt/idea.rb +193 -0
- data/rails/lib/flatulent/crypt/noise.rb +94 -0
- data/rails/lib/flatulent/crypt/purerubystringio.rb +378 -0
- data/rails/lib/flatulent/crypt/rijndael-tables.rb +117 -0
- data/rails/lib/flatulent/crypt/rijndael.rb +269 -0
- data/rails/lib/flatulent/fontfiles/banner.flf +2494 -0
- data/rails/lib/flatulent/fontfiles/big.flf +2204 -0
- data/rails/lib/flatulent/fontfiles/block.flf +1691 -0
- data/rails/lib/flatulent/fontfiles/bubble.flf +1630 -0
- data/rails/lib/flatulent/fontfiles/digital.flf +1286 -0
- data/rails/lib/flatulent/fontfiles/ivrit.flf +900 -0
- data/rails/lib/flatulent/fontfiles/lean.flf +1691 -0
- data/rails/lib/flatulent/fontfiles/mini.flf +899 -0
- data/rails/lib/flatulent/fontfiles/mnemonic.flf +3702 -0
- data/rails/lib/flatulent/fontfiles/script.flf +1493 -0
- data/rails/lib/flatulent/fontfiles/shadow.flf +1097 -0
- data/rails/lib/flatulent/fontfiles/slant.flf +1295 -0
- data/rails/lib/flatulent/fontfiles/small.flf +1097 -0
- data/rails/lib/flatulent/fontfiles/smscript.flf +1097 -0
- data/rails/lib/flatulent/fontfiles/smshadow.flf +899 -0
- data/rails/lib/flatulent/fontfiles/smslant.flf +1097 -0
- data/rails/lib/flatulent/fontfiles/standard.flf +2227 -0
- data/rails/lib/flatulent/fontfiles/term.flf +600 -0
- data/rails/lib/flatulent/pervasives.rb +32 -0
- data/rails/lib/flatulent/stringxor.rb +27 -0
- data/rails/lib/flatulent/text/double_metaphone.rb +356 -0
- data/rails/lib/flatulent/text/figlet/font.rb +117 -0
- data/rails/lib/flatulent/text/figlet/smusher.rb +64 -0
- data/rails/lib/flatulent/text/figlet/typesetter.rb +68 -0
- data/rails/lib/flatulent/text/figlet.rb +17 -0
- data/rails/lib/flatulent/text/levenshtein.rb +65 -0
- data/rails/lib/flatulent/text/metaphone.rb +97 -0
- data/rails/lib/flatulent/text/porter_stemming.rb +171 -0
- data/rails/lib/flatulent/text/soundex.rb +61 -0
- data/rails/lib/flatulent/text.rb +6 -0
- data/rails/lib/flatulent.rb +450 -0
- data/rails/log/development.log +14297 -0
- data/rails/log/fastcgi.crash.log +111 -0
- data/rails/log/lighttpd.access.log +3993 -0
- data/rails/log/lighttpd.error.log +111 -0
- data/rails/tmp/cache/javascripts/prototype.js-gzip-3275912-71260-1183440172 +0 -0
- data/rails/tmp/sessions/ruby_sess.32d68bc997054475 +0 -0
- data/rails/tmp/sessions/ruby_sess.4694a4b9bdf9bcf4 +0 -0
- data/rails/tmp/sessions/ruby_sess.99469fde69043a05 +0 -0
- data/rails/tmp/sessions/ruby_sess.a588c0a457345912 +0 -0
- data/rails/tmp/sessions/ruby_sess.b3344125a84a3efa +0 -0
- data/samples.rb +10 -0
- metadata +69 -3
- data/flatulent-0.0.0.gem +0 -0
@@ -0,0 +1,450 @@
|
|
1
|
+
# TODO - encrypt time
|
2
|
+
# TODO - noise is image chars
|
3
|
+
# TODO - newline in value??
|
4
|
+
# TODO - vertical offset in chars
|
5
|
+
|
6
|
+
class Flatulent
|
7
|
+
Flatulent::VERSION = '0.0.2' unless defined? Flatulent::VERSION
|
8
|
+
def self.flatulent() Flatulent::VERSION end
|
9
|
+
def self.libdir() File.expand_path(__FILE__).gsub(%r/\.rb$/, '') end
|
10
|
+
|
11
|
+
require 'cgi'
|
12
|
+
require 'base64'
|
13
|
+
require 'digest/md5'
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'rubygems'
|
17
|
+
rescue LoadError
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
$:.unshift libdir
|
22
|
+
require 'text/figlet'
|
23
|
+
require 'crypt/blowfish'
|
24
|
+
require 'pervasives'
|
25
|
+
require 'attributes'
|
26
|
+
ensure
|
27
|
+
$:.shift
|
28
|
+
end
|
29
|
+
|
30
|
+
class Error < ::StandardError; end
|
31
|
+
class EncryptionError < Error; end
|
32
|
+
class TimeBombError < Error; end
|
33
|
+
|
34
|
+
|
35
|
+
singleton_class =
|
36
|
+
class << self
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
singleton_class.module_eval do
|
41
|
+
|
42
|
+
attribute('fontdir'){ File.join libdir, 'fontfiles' }
|
43
|
+
|
44
|
+
attribute('style'){ Hash[
|
45
|
+
'white-space' => 'pre',
|
46
|
+
'font-family' => 'monospace',
|
47
|
+
'font-weight' => 'bold',
|
48
|
+
'font-size' => 'medium',
|
49
|
+
'background' => '#ffc',
|
50
|
+
'color' => '#00f',
|
51
|
+
'margin' => '2px',
|
52
|
+
'padding' => '2px',
|
53
|
+
'display' => 'table',
|
54
|
+
] }
|
55
|
+
|
56
|
+
attribute('noise_style'){ Hash[
|
57
|
+
'color' => '#ccc',
|
58
|
+
] }
|
59
|
+
|
60
|
+
attribute('key'){ default_key }
|
61
|
+
|
62
|
+
def valid? keywords = {}
|
63
|
+
begin
|
64
|
+
validate! keywords
|
65
|
+
true
|
66
|
+
rescue EncryptionError, TimeBombError
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate! keywords = {}
|
72
|
+
keywords = keywords['flatulent'] if keywords.has_key?('flatulent')
|
73
|
+
keywords = keywords[:flatulent] if keywords.has_key?(:flatulent)
|
74
|
+
|
75
|
+
opts = getopts keywords
|
76
|
+
|
77
|
+
captcha = opts['c'] or raise 'no captcha'
|
78
|
+
string = opts['s'] or raise 'no string'
|
79
|
+
time = opts['t'] or raise 'no time'
|
80
|
+
|
81
|
+
expected = zeroh(decrypt(string))
|
82
|
+
actual = zeroh(captcha)
|
83
|
+
raise EncryptionError, "expected #{ expected } got #{ actual }" unless
|
84
|
+
expected == actual
|
85
|
+
|
86
|
+
timebomb = Time.at(Integer(decrypt(time))).utc rescue(raise("bad time #{ time }"))
|
87
|
+
raise TimeBombError unless Time.now.utc <= timebomb
|
88
|
+
|
89
|
+
return actual
|
90
|
+
end
|
91
|
+
|
92
|
+
def zeroh string
|
93
|
+
string.gsub(%r/[0Oo]/, '0') # ignore diffs between 0/o/O
|
94
|
+
end
|
95
|
+
|
96
|
+
def blowfish
|
97
|
+
@blowfish ||= Hash.new{|h,k| h[k] = Crypt::Blowfish.new(key)}
|
98
|
+
end
|
99
|
+
|
100
|
+
def munge string
|
101
|
+
string.strip.downcase
|
102
|
+
end
|
103
|
+
|
104
|
+
def encrypt string
|
105
|
+
Base64.encode64(blowfish[key].encrypt_string(string.to_s)).chop # kill "\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
def decrypt string
|
109
|
+
munge(blowfish[key].decrypt_string(Base64.decode64("#{ string }\n")))
|
110
|
+
end
|
111
|
+
|
112
|
+
def getopts options
|
113
|
+
lambda do |key, *default|
|
114
|
+
default = default.first
|
115
|
+
break options[key] if options.has_key?(key)
|
116
|
+
key = key.to_s
|
117
|
+
break options[key] if options.has_key?(key)
|
118
|
+
key = key.to_sym
|
119
|
+
break options[key] if options.has_key?(key)
|
120
|
+
break default
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def default_key
|
125
|
+
#attribute('default_key') do
|
126
|
+
return @default_key if defined? @default_key
|
127
|
+
require 'socket'
|
128
|
+
hostname = Socket.gethostname
|
129
|
+
maddr = mac_address rescue nil
|
130
|
+
warn "could not determine mac addresss!" unless maddr
|
131
|
+
#puts(( Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--" ))
|
132
|
+
#Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--"
|
133
|
+
#@default_key = "--#{ hostname }--#{ maddr }--"
|
134
|
+
@default_key = "--#{ hostname }--#{ maddr }--"
|
135
|
+
end
|
136
|
+
|
137
|
+
def mac_address
|
138
|
+
return @mac_address if defined? @mac_address
|
139
|
+
re = %r/[^:\-](?:[0-9A-F][0-9A-F][:\-]){5}[0-9A-F][0-9A-F][^:\-]/io
|
140
|
+
cmds = '/sbin/ifconfig', '/bin/ifconfig', 'ifconfig', 'ipconfig /all'
|
141
|
+
|
142
|
+
null = test(?e, '/dev/null') ? '/dev/null' : 'NUL'
|
143
|
+
|
144
|
+
lines = nil
|
145
|
+
cmds.each do |cmd|
|
146
|
+
stdout = IO.popen("#{ cmd } 2> #{ null }"){|fd| fd.readlines} rescue next
|
147
|
+
next unless stdout and stdout.size > 0
|
148
|
+
lines = stdout and break
|
149
|
+
end
|
150
|
+
raise "all of #{ cmds.join ' ' } failed" unless lines
|
151
|
+
|
152
|
+
candidates = lines.select{|line| line =~ re}
|
153
|
+
raise 'no mac address candidates' unless candidates.first
|
154
|
+
candidates.map!{|c| c[re]}
|
155
|
+
|
156
|
+
maddr = candidates.first
|
157
|
+
raise 'no mac address found' unless maddr
|
158
|
+
|
159
|
+
maddr.strip!
|
160
|
+
maddr.instance_eval{ @list = candidates; def list() @list end }
|
161
|
+
|
162
|
+
@mac_address = maddr
|
163
|
+
end
|
164
|
+
|
165
|
+
def figlet options = {}
|
166
|
+
new(options).figlet
|
167
|
+
end
|
168
|
+
def figlets options = {}
|
169
|
+
new(options).figlets
|
170
|
+
end
|
171
|
+
def element options = {}
|
172
|
+
new(options).element
|
173
|
+
end
|
174
|
+
def form_tags options = {}
|
175
|
+
new(options).form_tags
|
176
|
+
end
|
177
|
+
def form options = {}
|
178
|
+
new(options).form
|
179
|
+
end
|
180
|
+
|
181
|
+
def latest_prototype_lib
|
182
|
+
'http://www.prototypejs.org/assets/2007/6/20/prototype.js'
|
183
|
+
end
|
184
|
+
def require_prototype
|
185
|
+
%Q`
|
186
|
+
<script type="text/javascript">
|
187
|
+
var prototype = true;
|
188
|
+
try{ Prototype; } catch(e) { prototype = false };
|
189
|
+
if(!prototype){
|
190
|
+
var js = document.createElement('script');
|
191
|
+
js.type='text/javascript';
|
192
|
+
js.src='/javascripts/prototype.js';
|
193
|
+
document.getElementsByTagName('head')[0].appendChild(js);
|
194
|
+
}
|
195
|
+
</script>
|
196
|
+
<script type="text/javascript">
|
197
|
+
var prototype = true;
|
198
|
+
try{ Prototype; } catch(e) { prototype = false };
|
199
|
+
if(!prototype){
|
200
|
+
var js = document.createElement('script');
|
201
|
+
js.type='text/javascript';
|
202
|
+
js.src='#{ latest_prototype_lib }';
|
203
|
+
document.getElementsByTagName('head')[0].appendChild(js);
|
204
|
+
}
|
205
|
+
</script>
|
206
|
+
`
|
207
|
+
end
|
208
|
+
def javascript options = {}
|
209
|
+
id = options[:id] || options['id'] || 'flatulent'
|
210
|
+
url = options[:url] || options['url'] || '/flatulent/captcha'
|
211
|
+
%Q`
|
212
|
+
#{ require_prototype }
|
213
|
+
<script type="text/javascript">
|
214
|
+
new Ajax.Updater('#{ id }', '#{ url }', { method: 'get' });
|
215
|
+
</script>
|
216
|
+
`
|
217
|
+
end
|
218
|
+
def ajax options = {}
|
219
|
+
id = options[:id] || options['id'] || 'flatulent'
|
220
|
+
%Q`
|
221
|
+
<div id="#{ id }"></div>
|
222
|
+
#{ Flatulent.javascript }
|
223
|
+
`
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
singleton_class.attributes.each{|a| attribute(a){ self.class.send a}}
|
228
|
+
|
229
|
+
attribute 'string'
|
230
|
+
attribute 'size'
|
231
|
+
attribute 'font'
|
232
|
+
attribute 'noise'
|
233
|
+
attribute 'id'
|
234
|
+
attribute 'action'
|
235
|
+
attribute 'ttl'
|
236
|
+
|
237
|
+
attribute 'figlet'
|
238
|
+
attribute 'figlets'
|
239
|
+
attribute 'element'
|
240
|
+
attribute 'form_tags'
|
241
|
+
attribute 'form'
|
242
|
+
|
243
|
+
def initialize arg = {}
|
244
|
+
if Hash === arg
|
245
|
+
opt = getopts arg
|
246
|
+
@size = Integer opt[ 'size', 4 ]
|
247
|
+
@string = String opt[ 'string', generate_random_string ]
|
248
|
+
else
|
249
|
+
opt = getopts Hash.new
|
250
|
+
@string = String arg
|
251
|
+
@size = @string.size
|
252
|
+
end
|
253
|
+
|
254
|
+
@font = String opt[ 'font', 'big' ]
|
255
|
+
@noise = Float opt[ 'noise', 0.03 ]
|
256
|
+
@id = String opt[ 'id', 'flatulent' ]
|
257
|
+
@action = String opt[ 'action' ]
|
258
|
+
@ttl = Integer opt[ 'ttl', 256 ]
|
259
|
+
|
260
|
+
figlet!
|
261
|
+
element!
|
262
|
+
form_tags!
|
263
|
+
form!
|
264
|
+
end
|
265
|
+
|
266
|
+
def generate_random_string
|
267
|
+
chars = ('A' .. 'Z').to_a + ('1' .. '9').to_a ### zero is too much like o/O
|
268
|
+
Array.new(@size).map{ chars[rand(chars.size - 1)]}.join
|
269
|
+
end
|
270
|
+
|
271
|
+
def figlet!
|
272
|
+
spaced = @string.split(%r//).join #.join(' ')
|
273
|
+
fontfile = File.join fontdir, "#{ @font }.flf"
|
274
|
+
font = Text::Figlet::Font.new fontfile
|
275
|
+
typesetter = Text::Figlet::Typesetter.new font
|
276
|
+
@figlets = []
|
277
|
+
chars = spaced.split %r//
|
278
|
+
chars.each{|char| @figlets << typesetter[char]}
|
279
|
+
@figlet = typesetter[spaced]
|
280
|
+
end
|
281
|
+
|
282
|
+
def element!
|
283
|
+
rows = []
|
284
|
+
rows << (row = [])
|
285
|
+
chars = @figlet.split %r//
|
286
|
+
size = chars.size
|
287
|
+
last = size - 1
|
288
|
+
|
289
|
+
chars.each_with_index do |char, idx|
|
290
|
+
content =
|
291
|
+
case char
|
292
|
+
when %r/\n/o
|
293
|
+
"<br>"
|
294
|
+
when %r/\s/o
|
295
|
+
#rand > 0.42 ? " " : " "
|
296
|
+
" "
|
297
|
+
when %r/([^\s])/o
|
298
|
+
CGI.escapeHTML $1
|
299
|
+
end
|
300
|
+
Array.new(rand(42)){ content = "<span>#{ content }</span>"}
|
301
|
+
row << content
|
302
|
+
rows << (row = []) unless idx == last
|
303
|
+
end
|
304
|
+
|
305
|
+
noisy = %W` | / - _ ( ) \ `
|
306
|
+
(@noise * chars.size).ceil.times do
|
307
|
+
y = rand(rows.size - 1)
|
308
|
+
x = rand(rows.first.size - 1)
|
309
|
+
next if rows[y][x] =~ %r"<br>"
|
310
|
+
char = noisy[ rand(noisy.size) ]
|
311
|
+
rows[y][x] = char
|
312
|
+
end
|
313
|
+
|
314
|
+
content = rows.join
|
315
|
+
@element = "<pre id='#{ @id }_element' style='#{ css }'>#{ content }</pre>"
|
316
|
+
end
|
317
|
+
|
318
|
+
=begin
|
319
|
+
|
320
|
+
def element!
|
321
|
+
cells = []
|
322
|
+
|
323
|
+
@figlets.each do |figlet|
|
324
|
+
rows = []
|
325
|
+
rows << (row = [])
|
326
|
+
|
327
|
+
offset_t = Array.new(rand(4)).map{ "\n"}
|
328
|
+
offset_b = Array.new(rand(4)).map{ "\n"}
|
329
|
+
|
330
|
+
offset_l = Array.new(rand(4)).map{ " "}
|
331
|
+
offset_r = Array.new(rand(4)).map{ " "}
|
332
|
+
|
333
|
+
chars = offset_t + figlet.split(%r//) + offset_b
|
334
|
+
size = chars.size
|
335
|
+
last = size - 1
|
336
|
+
#drawn = chars.select{|char| char !~ %r/\s/}
|
337
|
+
drawn = %w` | / \ < > v ^ - _ ( ) `
|
338
|
+
|
339
|
+
chars.each_with_index do |char, idx|
|
340
|
+
content =
|
341
|
+
case char
|
342
|
+
when %r/\n/o
|
343
|
+
"<br>"
|
344
|
+
when %r/\s/o
|
345
|
+
#rand > 0.42 ? " " : " "
|
346
|
+
" "
|
347
|
+
when %r/([^\s])/o
|
348
|
+
CGI.escapeHTML $1
|
349
|
+
end
|
350
|
+
#rand(10).times{ content = "<span>#{ content }</span>" }
|
351
|
+
row << content
|
352
|
+
rows << (row = []) unless idx == last
|
353
|
+
end
|
354
|
+
|
355
|
+
noisy = %w` | / \ - _ ( ) `
|
356
|
+
(@noise * chars.size).ceil.times do
|
357
|
+
y = rand(rows.size - 1)
|
358
|
+
x = rand(rows.first.size - 1)
|
359
|
+
next if rows[y][x] == "<br>"
|
360
|
+
char = noisy[ rand(noisy.size) ]
|
361
|
+
rows[y][x] = char
|
362
|
+
end
|
363
|
+
|
364
|
+
content = rows.join
|
365
|
+
cells << content
|
366
|
+
end
|
367
|
+
|
368
|
+
formatted = lambda{|x| "<pre class='#{ @id }_figlet' style='#{ css }'>#{ x }</pre>"}
|
369
|
+
|
370
|
+
@element =
|
371
|
+
"<table id='#{ @id }_element' border='0' cellpadding='0' cellspacing='0' bgcolor='#{ style["background"] }'><tr>" <<
|
372
|
+
cells.map{|cell| "<td>#{ formatted[cell] }</td>"}.join <<
|
373
|
+
"</tr></table>"
|
374
|
+
end
|
375
|
+
=end
|
376
|
+
|
377
|
+
def css
|
378
|
+
css_for style
|
379
|
+
end
|
380
|
+
def noise_css
|
381
|
+
css_for noise_style
|
382
|
+
end
|
383
|
+
|
384
|
+
def css_for hash
|
385
|
+
hash.map{|kv| kv.join ':'}.join ';'
|
386
|
+
end
|
387
|
+
|
388
|
+
def form_tags!
|
389
|
+
n = @string.scan(%r/\w/).size
|
390
|
+
string = @string
|
391
|
+
timebomb = Time.now.utc.to_i + @ttl
|
392
|
+
@form_tags = <<-html
|
393
|
+
#{ element }
|
394
|
+
<p id='#{ @id }_instructions'>
|
395
|
+
Please enter the #{ n } large characters (A-Z, 1-9) shown.
|
396
|
+
</p>
|
397
|
+
<input type='textarea' name='#{ @id }[c]' id='#{ @id }_textarea' />
|
398
|
+
<input type='hidden' name='#{ @id }[s]' id='#{ @id }_e' value='#{ encrypt string }' />
|
399
|
+
<input type='hidden' name='#{ @id }[t]' id='#{ @id }_v' value='#{ encrypt timebomb }' />
|
400
|
+
html
|
401
|
+
end
|
402
|
+
alias_method 'to_html', 'form_tags'
|
403
|
+
|
404
|
+
def encrypt string
|
405
|
+
self.class.encrypt string.to_s
|
406
|
+
end
|
407
|
+
|
408
|
+
def encrypted
|
409
|
+
self.class.encrypt @string
|
410
|
+
end
|
411
|
+
|
412
|
+
def munge string
|
413
|
+
self.class.munge string
|
414
|
+
end
|
415
|
+
|
416
|
+
def form!
|
417
|
+
action = "action='#{ @action }'"
|
418
|
+
@form = <<-html
|
419
|
+
<form method='post' #{ action }>
|
420
|
+
#{ form_tags }
|
421
|
+
<input type='submit' name='#{ @id }[submit]' id='#{ @id }_submit' value='Submit' />
|
422
|
+
</form>
|
423
|
+
html
|
424
|
+
end
|
425
|
+
|
426
|
+
def to_html
|
427
|
+
element
|
428
|
+
end
|
429
|
+
|
430
|
+
def to_s
|
431
|
+
form
|
432
|
+
end
|
433
|
+
|
434
|
+
def getopts options
|
435
|
+
self.class.getopts options
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def Flatulent(*a, &b) Flatulent.new(*a, &b) end
|
440
|
+
|
441
|
+
if $0 == __FILE__
|
442
|
+
#string = rand.to_s
|
443
|
+
#puts Flatulent(string)
|
444
|
+
#Flatulent.validate! :t => string, :e => Flatulent.encrypt(string), :v => (Time.now.utc.to_i + 60)
|
445
|
+
#e = Flatulent.encrypt('foobar')
|
446
|
+
#p e
|
447
|
+
#p Flatulent.decrypt(e)
|
448
|
+
|
449
|
+
puts Flatulent.figlet('foobar')
|
450
|
+
end
|