spambust 0.1.4 → 0.2.0
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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +31 -0
- data/LICENSE +1 -1
- data/README.md +23 -20
- data/lib/spambust.rb +7 -1
- data/lib/spambust/form_helpers.rb +96 -80
- data/lib/spambust/version.rb +8 -2
- data/spec/spambust/demo_app.rb +17 -8
- data/spec/spambust/form_helpers_integration_spec.rb +45 -36
- data/spec/spambust/form_helpers_spec.rb +91 -58
- data/spec/spambust/views/index.erb +15 -15
- data/spec/spambust/views/layout.erb +6 -6
- data/spec/spec_helper.rb +20 -8
- metadata +58 -32
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NDY1NDEzNWIxNzU5MjE4OWJiZDJhYzRlZTIzMmU5OWQ2MDE1MDMxMg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fa2b64be89004072d0059f05723151f73a279afb
|
4
|
+
data.tar.gz: c867ae7f94d6be8d8f42be325f938e093cc7a551
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NjFmM2IwN2Q2NTYxZjA0ZTAwMGU3ZmFlZGFmYmQxYjQ2MTk3ZDliNGQxOGUy
|
11
|
-
ZWFmYjhkY2MwNzQ1NzJlNjUyNTk0OGZkMTVlY2Q1OTU2YjM2YjA=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NzM0NWZkM2FkYjc5YjZhNDg0ODc1ODYzZmY1YWE1ZDFhMzc0NTI1NDVjMDlh
|
14
|
-
ZDVhZWE5OTU3YTczOGJhNTY5NTgxNDAxZmNlY2VkNzU3OTEwNGVjMzRhY2Yy
|
15
|
-
NjUxNjA1NDcxMTI4OTllOWZjZjM0MmEyMTg4ZTJhM2Y5ZDc4MjA=
|
6
|
+
metadata.gz: 10de6ad17e986655a2a413a8703e7c1b6d49e63b926759ee3174c189b9c5143c6fdea28f8666f90ab11f4a31095d5b6edc646c0dcb30c5f3ecf176dcdca1d1ca
|
7
|
+
data.tar.gz: 23fa457d817bde24764c503b217c8b85a87402677aa8b3d536837c8ad59b255de0d51dbf50164d1f00068b54d62b69f927b49a1e3c4f868fb559edba3e59c9e9
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
v0.2.0
|
2
|
+
======
|
3
|
+
|
4
|
+
* Drop support for ruby 1.8.x
|
5
|
+
* Remove CSS class, style options from off-screen tag
|
6
|
+
|
7
|
+
v0.1.4
|
8
|
+
======
|
9
|
+
|
10
|
+
* Include license in gem specification
|
11
|
+
|
12
|
+
v0.1.3
|
13
|
+
======
|
14
|
+
|
15
|
+
* Render trap elements off screen. A spam bot might not fill hidden fields
|
16
|
+
* Measure code coverage
|
17
|
+
|
18
|
+
v0.1.2
|
19
|
+
======
|
20
|
+
|
21
|
+
* Add documentation
|
22
|
+
|
23
|
+
v0.1.1
|
24
|
+
======
|
25
|
+
|
26
|
+
* Add decryption logic to the gem
|
27
|
+
|
28
|
+
v0.1.0
|
29
|
+
======
|
30
|
+
|
31
|
+
* Initial release
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
[](https://travis-ci.org/chiku/spambust)
|
2
|
+
[](http://badge.fury.io/rb/spambust)
|
2
3
|
[](https://codeclimate.com/github/chiku/spambust)
|
3
4
|
|
4
5
|
spambust
|
@@ -26,7 +27,7 @@ Usage
|
|
26
27
|
|
27
28
|
**app.rb**
|
28
29
|
|
29
|
-
```
|
30
|
+
```ruby
|
30
31
|
class TestApp < Sinatra::Base
|
31
32
|
helpers Spambust::FormHelpers
|
32
33
|
|
@@ -36,7 +37,7 @@ class TestApp < Sinatra::Base
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def direct_script_execution?
|
39
|
-
app_file == $
|
40
|
+
app_file == $PROGRAM_NAME
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -55,27 +56,27 @@ end
|
|
55
56
|
|
56
57
|
**index.erb**
|
57
58
|
|
58
|
-
```
|
59
|
+
```erb
|
59
60
|
<html>
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
<head>
|
62
|
+
<title>Sample Sinatra application</title>
|
63
|
+
</head>
|
64
|
+
<body>
|
65
|
+
<div id="result"><%= result %></div>
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
<form method="post" action="/">
|
68
|
+
<label for="user-first-name">First name</label>
|
69
|
+
<%= input ["user", "first_name"], :id => "user-first-name" %>
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
<label for="user-last-name">Last name</label>
|
72
|
+
<%= input ["user", "last_name"], :id => "user-last-name" %>
|
72
73
|
|
73
|
-
|
74
|
-
|
74
|
+
<label for="user-email">Email</label>
|
75
|
+
<%= input ["user", "email"], :size => 40, :id => "user-email" %>
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
<%= submit "Create account", :id => "user-submit" %>
|
78
|
+
</form>
|
79
|
+
</body>
|
79
80
|
</html>
|
80
81
|
```
|
81
82
|
|
@@ -89,12 +90,14 @@ end
|
|
89
90
|
How does it work?
|
90
91
|
-----------------
|
91
92
|
|
92
|
-
The server will render
|
93
|
+
The server will render obfuscated input tags for the user to fill. The input tags for the user will be hidden. A spam bot would see the input tags will meaningful names and fill it in. The server will figure out that this response came from a bot and take approriate action.
|
93
94
|
|
94
95
|
Running tests
|
95
96
|
-------------
|
96
97
|
|
97
|
-
Clone the repository
|
98
|
+
1. Clone the repository.
|
99
|
+
2. run `bundle` from the root directory.
|
100
|
+
3. run `rake` from the root directory.
|
98
101
|
|
99
102
|
Contributing
|
100
103
|
------------
|
data/lib/spambust.rb
CHANGED
@@ -1,139 +1,155 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# form_helper.rb
|
1
|
+
# form_helpers.rb
|
4
2
|
#
|
5
3
|
# Author:: Chirantan Mitra
|
6
|
-
# Copyright:: Copyright (c) 2013. All rights reserved
|
4
|
+
# Copyright:: Copyright (c) 2013-2016. All rights reserved
|
7
5
|
# License:: MIT
|
8
|
-
#
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
# helpers Spambust::FormHelpers
|
16
|
-
#
|
17
|
-
# class << self
|
18
|
-
# def start_app
|
19
|
-
# run!
|
20
|
-
# end
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
module Spambust # :nodoc:
|
10
|
+
##
|
11
|
+
# <b>Form helpers for sinatra or similar DSLs/frameworks to block for spams</b>
|
21
12
|
#
|
22
|
-
#
|
23
|
-
# app_file == $0
|
24
|
-
# end
|
25
|
-
# end
|
13
|
+
# == Examples
|
26
14
|
#
|
27
|
-
#
|
28
|
-
# erb :index, :locals => { :result => "..." }
|
29
|
-
# end
|
15
|
+
# === app.rb
|
30
16
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# erb :index, :locals => { :result => result }
|
34
|
-
# end
|
17
|
+
# class TestApp < Sinatra::Base
|
18
|
+
# helpers Spambust::FormHelpers
|
35
19
|
#
|
36
|
-
#
|
37
|
-
#
|
20
|
+
# class << self
|
21
|
+
# def start_app
|
22
|
+
# run!
|
23
|
+
# end
|
38
24
|
#
|
39
|
-
#
|
25
|
+
# def direct_script_execution?
|
26
|
+
# app_file == $PROGRAM_NAME
|
27
|
+
# end
|
28
|
+
# end
|
40
29
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# </head>
|
45
|
-
# <body>
|
46
|
-
# <div id="result"><%= result %></div>
|
30
|
+
# get '/' do
|
31
|
+
# erb :index, :locals => { :result => '...' }
|
32
|
+
# end
|
47
33
|
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
34
|
+
# post '/' do
|
35
|
+
# valid = valid?('user', params)
|
36
|
+
# result = valid ? "Users is #{decrypt('user', params)}" : 'Faker!'
|
37
|
+
# erb :index, locals: { result: result }
|
38
|
+
# end
|
51
39
|
#
|
52
|
-
#
|
53
|
-
#
|
40
|
+
# start_app if direct_script_execution? && ENV['environment'] != 'test'
|
41
|
+
# end
|
54
42
|
#
|
55
|
-
#
|
56
|
-
# <%= input ["user", "email"], :size => 40, :id => "user-email" %>
|
43
|
+
# === index.erb
|
57
44
|
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
45
|
+
# <html>
|
46
|
+
# <head>
|
47
|
+
# <title>Sample Sinatra application</title>
|
48
|
+
# </head>
|
49
|
+
# <body>
|
50
|
+
# <div id="result"><%= result %></div>
|
51
|
+
# <form method="post" action="/">
|
52
|
+
# <label for="user-first-name">First name</label>
|
53
|
+
# <%= input ["user", "first_name"], :id => "user-first-name" %>
|
54
|
+
# <label for="user-last-name">Last name</label>
|
55
|
+
# <%= input ["user", "last_name"], :id => "user-last-name" %>
|
56
|
+
# <label for="user-email">Email</label>
|
57
|
+
# <%= input ["user", "email"], :size => 40, :id => "user-email" %>
|
58
|
+
# <%= submit "Create account", :id => "user-submit" %>
|
59
|
+
# </form>
|
60
|
+
# </body>
|
61
|
+
# </html>
|
63
62
|
module FormHelpers
|
64
|
-
|
63
|
+
HIDING = 'position:absolute;top:-10000px;left:-10000px;' # :nodoc:
|
64
|
+
BLOCKED_OPTIONS = [:id, :class, :style] # :nodoc:
|
65
|
+
|
66
|
+
##
|
67
|
+
# Returns obfuscated input tags together with its fake input tags that are
|
68
|
+
# rendered off the screen
|
65
69
|
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
70
|
+
# Use inside your templates to generate an obfuscated input field. This is
|
71
|
+
# the field that the server will use. If the server sees that fields with
|
72
|
+
# original names are filled, the server should assume it be be a spam.
|
73
|
+
# It also accepts options for input type and other CSS properties.
|
69
74
|
#
|
70
|
-
# input([
|
71
|
-
# # => <input type="text" name="#{
|
75
|
+
# input(['user', 'name'])
|
76
|
+
# # => <input type="text" name="#{user_digest}[#{name_digest}]" />\
|
77
|
+
# # <input type="text" style="position:absolute;top:-10000px;left:-10000px;" name="user[name]" />
|
72
78
|
#
|
73
|
-
# input([
|
74
|
-
# # => <input type="password" name="#{
|
79
|
+
# input(['user', 'name'], type: 'password')
|
80
|
+
# # => <input type="password" name="#{user_digest}[#{name_digest}]" />\
|
81
|
+
# # <input type="text" style="position:absolute;top:-10000px;left:-10000px;" name="user[name]" />
|
75
82
|
#
|
76
|
-
# input([
|
77
|
-
# # => <input type="text" name="#{
|
83
|
+
# input(['user', 'name'], id: 'name', class: 'name')
|
84
|
+
# # => <input type="text" name="#{user_digest}[#{name_digest}]" id="name" class="name" />\
|
85
|
+
# # <input type="text" style="position:absolute;top:-10000px;left:-10000px;" name="user[name]" class="name" />
|
78
86
|
def input(paths, options = {})
|
79
|
-
type
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
%
|
87
|
+
type = options.delete(:type) || 'text'.freeze
|
88
|
+
sanitized_options = options.reject { |key, _value| BLOCKED_OPTIONS.include?(key) }
|
89
|
+
digested_paths = paths.map { |path| digest(path) }
|
90
|
+
visible_tag_options = options.merge(type: type, name: namify(digested_paths))
|
91
|
+
hidden_tag_options = sanitized_options.merge(type: 'text', name: namify(paths), style: HIDING)
|
92
|
+
visible_tag = %(<input #{hash_to_options visible_tag_options} />)
|
93
|
+
hidden_tag = %(<input #{hash_to_options hidden_tag_options} />)
|
94
|
+
"#{visible_tag}#{hidden_tag}"
|
85
95
|
end
|
86
96
|
|
97
|
+
##
|
87
98
|
# Returns submit tags
|
88
99
|
#
|
89
|
-
#
|
90
|
-
#
|
100
|
+
# Use inside your templates to generate a submit tag.
|
101
|
+
# It also accepts for CSS options.
|
91
102
|
#
|
92
|
-
# submit(
|
103
|
+
# submit('Submit')
|
93
104
|
# # => <input type="submit" value="Submit" />
|
94
105
|
#
|
95
106
|
# submit("Submit", :id => "submit", :class => "submit")
|
96
107
|
# # => <input type="submit" value="Submit" id="submit" class="submit" />
|
97
108
|
def submit(text, options = {})
|
98
|
-
|
99
|
-
%
|
109
|
+
visible_tag_options = options.merge(type: 'submit'.freeze, value: text)
|
110
|
+
%(<input #{hash_to_options visible_tag_options} />).gsub(' ', ' ')
|
100
111
|
end
|
101
112
|
|
102
113
|
def namify(paths) # :nodoc:
|
103
114
|
first = paths[0]
|
104
|
-
rest = paths[1..-1].reduce(
|
115
|
+
rest = paths[1..-1].reduce('') { |a, e| a << "[#{e}]" }
|
105
116
|
"#{first}#{rest}"
|
106
117
|
end
|
107
118
|
|
119
|
+
##
|
108
120
|
# Returns decrypted hash of user submitted POST parameters
|
109
|
-
#
|
110
|
-
# Use inside your application.
|
121
|
+
# Use inside your application.
|
111
122
|
#
|
112
123
|
# decrypt("user", params)
|
113
124
|
def decrypt(lookup, global)
|
114
125
|
fake = global[lookup] || {}
|
115
|
-
hashed_lookup =
|
126
|
+
hashed_lookup = digest(lookup)
|
116
127
|
subset = global[hashed_lookup] || {}
|
117
128
|
|
118
|
-
fake.
|
119
|
-
real[key] = subset[
|
120
|
-
real
|
129
|
+
fake.each_with_object({}) do |(key, _value), real|
|
130
|
+
real[key] = subset[digest(key)]
|
121
131
|
end
|
122
132
|
end
|
123
133
|
|
134
|
+
##
|
124
135
|
# Returns if any POST data was present in the fake input fields
|
125
136
|
#
|
126
|
-
#
|
137
|
+
# Use inside your application.
|
127
138
|
#
|
128
|
-
# valid?(
|
139
|
+
# valid?('user', params)
|
129
140
|
def valid?(lookup, global)
|
130
141
|
fake = global[lookup] || {}
|
131
|
-
fake.
|
142
|
+
fake.values.all?(&:empty?)
|
132
143
|
end
|
133
144
|
|
134
145
|
def hash_to_options(hash) # :nodoc:
|
135
|
-
hash.
|
146
|
+
hash.map { |key, value| %(#{key}="#{value}") }.join(' ')
|
136
147
|
end
|
137
148
|
private :hash_to_options
|
149
|
+
|
150
|
+
def digest(item) # :nodoc:
|
151
|
+
Digest::MD5.hexdigest(item)
|
152
|
+
end
|
153
|
+
private :digest
|
138
154
|
end
|
139
155
|
end
|
data/lib/spambust/version.rb
CHANGED
data/spec/spambust/demo_app.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
# demo_app.rb
|
4
|
+
#
|
5
|
+
# Author:: Chirantan Mitra
|
6
|
+
# Copyright:: Copyright (c) 2013-2016. All rights reserved
|
7
|
+
# License:: MIT
|
8
|
+
|
9
|
+
require 'tilt/erb'
|
10
|
+
require 'sinatra'
|
11
|
+
|
12
|
+
require_relative '../../lib/spambust/form_helpers'
|
5
13
|
|
6
14
|
module Spambust
|
7
15
|
class TestApp < Sinatra::Base
|
@@ -13,19 +21,20 @@ module Spambust
|
|
13
21
|
end
|
14
22
|
|
15
23
|
def direct_script_execution?
|
16
|
-
app_file == $
|
24
|
+
app_file == $PROGRAM_NAME
|
17
25
|
end
|
18
26
|
end
|
19
27
|
|
20
|
-
get
|
21
|
-
erb :index, :
|
28
|
+
get '/' do
|
29
|
+
erb :index, locals: { result: '...' }
|
22
30
|
end
|
23
31
|
|
24
32
|
post '/' do
|
25
|
-
|
26
|
-
|
33
|
+
valid = valid?('user', params)
|
34
|
+
result = valid ? "User is #{decrypt('user', params)}" : 'Faking is bad'
|
35
|
+
erb :index, locals: { result: result }
|
27
36
|
end
|
28
37
|
|
29
|
-
start_app if direct_script_execution? && ENV[
|
38
|
+
start_app if direct_script_execution? && ENV['environment'] != 'test'
|
30
39
|
end
|
31
40
|
end
|
@@ -1,62 +1,71 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# form_helpers_integration_spec.rb
|
2
|
+
#
|
3
|
+
# Author:: Chirantan Mitra
|
4
|
+
# Copyright:: Copyright (c) 2013-2016. All rights reserved
|
5
|
+
# License:: MIT
|
3
6
|
|
4
|
-
|
7
|
+
require_relative '../spec_helper'
|
8
|
+
require_relative 'demo_app'
|
9
|
+
|
10
|
+
describe 'Test application' do
|
5
11
|
include Rack::Test::Methods
|
6
12
|
|
7
|
-
let(:app)
|
13
|
+
let(:app) { Spambust::TestApp }
|
8
14
|
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:
|
15
|
+
let(:user_digest) { Digest::MD5.hexdigest('user') }
|
16
|
+
let(:first_name_digest) { Digest::MD5.hexdigest('first_name') }
|
17
|
+
let(:last_name_digest) { Digest::MD5.hexdigest('last_name') }
|
18
|
+
let(:email_digest) { Digest::MD5.hexdigest('email') }
|
19
|
+
let(:hiding) { 'position:absolute;top:-10000px;left:-10000px;' }
|
13
20
|
|
14
|
-
describe
|
15
|
-
it
|
16
|
-
get
|
21
|
+
describe 'when loading a form' do
|
22
|
+
it 'contains hidden input fields' do
|
23
|
+
get '/'
|
17
24
|
|
18
|
-
last_response.body.must_include %
|
25
|
+
last_response.body.must_include %(<input size="40" type="text" \
|
26
|
+
name="user[email]" style="#{hiding}" />)
|
19
27
|
end
|
20
28
|
end
|
21
29
|
|
22
|
-
describe
|
30
|
+
describe 'when posting a form' do
|
23
31
|
describe "when hidden fields aren't filled" do
|
24
|
-
it
|
32
|
+
it 'is accepted' do
|
25
33
|
params = {
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
user_digest => {
|
35
|
+
first_name_digest => 'True first name',
|
36
|
+
last_name_digest => 'True last name',
|
37
|
+
email_digest => 'True email'
|
30
38
|
},
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
'user' => {
|
40
|
+
'first_name' => '',
|
41
|
+
'last_name' => '',
|
42
|
+
'email' => ''
|
35
43
|
}
|
36
44
|
}
|
37
45
|
|
38
|
-
post
|
39
|
-
last_response.body.must_include %
|
46
|
+
post '/', params
|
47
|
+
last_response.body.must_include %({"first_name"=>"True first name", \
|
48
|
+
"last_name"=>"True last name", "email"=>"True email"})
|
40
49
|
end
|
41
50
|
end
|
42
51
|
|
43
|
-
describe
|
44
|
-
it
|
52
|
+
describe 'when hidden fields are filled' do
|
53
|
+
it 'is identified as fake' do
|
45
54
|
params = {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
user_digest => {
|
56
|
+
first_name_digest => 'True first name',
|
57
|
+
last_name_digest => 'True last name',
|
58
|
+
email_digest => 'True email'
|
50
59
|
},
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
'user' => {
|
61
|
+
'first_name' => 'Fake first name',
|
62
|
+
'last_name' => 'Fake last name',
|
63
|
+
'email' => 'Fake email'
|
55
64
|
}
|
56
65
|
}
|
57
66
|
|
58
|
-
post
|
59
|
-
last_response.body.must_include
|
67
|
+
post '/', params
|
68
|
+
last_response.body.must_include 'Faking is bad'
|
60
69
|
end
|
61
70
|
end
|
62
71
|
end
|
@@ -1,134 +1,167 @@
|
|
1
|
-
|
1
|
+
# form_helpers_spec.rb
|
2
|
+
#
|
3
|
+
# Author:: Chirantan Mitra
|
4
|
+
# Copyright:: Copyright (c) 2013-2016. All rights reserved
|
5
|
+
# License:: MIT
|
2
6
|
|
3
|
-
require
|
4
|
-
|
7
|
+
require 'digest/md5'
|
8
|
+
|
9
|
+
require_relative '../spec_helper'
|
10
|
+
require_relative '../../lib/spambust/form_helpers'
|
5
11
|
|
6
12
|
class TestApp
|
7
13
|
include Spambust::FormHelpers
|
8
14
|
end
|
9
15
|
|
10
|
-
describe
|
16
|
+
describe 'Spambust::FormHelpers' do
|
11
17
|
subject { TestApp.new }
|
12
18
|
|
13
|
-
let(:
|
14
|
-
let(:
|
19
|
+
let(:user_digest) { Digest::MD5.hexdigest('user') }
|
20
|
+
let(:name_digest) { Digest::MD5.hexdigest('name') }
|
21
|
+
let(:hiding) { 'position:absolute;top:-10000px;left:-10000px;' }
|
15
22
|
|
16
|
-
describe
|
17
|
-
describe
|
23
|
+
describe '#input' do
|
24
|
+
describe 'when type is not mentioned' do
|
18
25
|
it "renders an input tag of type 'text'" do
|
19
|
-
subject.input(
|
26
|
+
subject.input(%w(user name)).must_equal %(\
|
27
|
+
<input type="text" name="#{user_digest}[#{name_digest}]" />\
|
28
|
+
<input type="text" name="user[name]" style="#{hiding}" />)
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
|
-
describe
|
24
|
-
it
|
25
|
-
subject.input(
|
32
|
+
describe 'when type is mentioned' do
|
33
|
+
it 'renders an input tag of specified type' do
|
34
|
+
subject.input(%w(user name), type: 'password').must_equal %(\
|
35
|
+
<input type="password" name="#{user_digest}[#{name_digest}]" />\
|
36
|
+
<input type="text" name="user[name]" style="#{hiding}" />)
|
26
37
|
end
|
27
38
|
end
|
28
39
|
|
29
|
-
describe
|
30
|
-
it
|
31
|
-
subject.input(
|
40
|
+
describe 'when CSS options are mentioned' do
|
41
|
+
it 'renders the options' do
|
42
|
+
subject.input(%w(user name), maxlength: '40').must_equal %(\
|
43
|
+
<input maxlength="40" type="text" name="#{user_digest}[#{name_digest}]" />\
|
44
|
+
<input maxlength="40" type="text" name="user[name]" style="#{hiding}" />)
|
32
45
|
end
|
33
46
|
end
|
34
47
|
|
35
|
-
describe "when
|
48
|
+
describe "when CSS options include 'id'" do
|
36
49
|
it "doesn't repeat the 'id'" do
|
37
|
-
subject.input(
|
50
|
+
subject.input(%w(user name), id: 'name').must_equal %(\
|
51
|
+
<input id="name" type="text" name="#{user_digest}[#{name_digest}]" />\
|
52
|
+
<input type="text" name="user[name]" style="#{hiding}" />)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "when CSS options include 'class'" do
|
57
|
+
it "doesn't repeat the 'class'" do
|
58
|
+
subject.input(%w(user name), class: 'name').must_equal %(\
|
59
|
+
<input class="name" type="text" name="#{user_digest}[#{name_digest}]" />\
|
60
|
+
<input type="text" name="user[name]" style="#{hiding}" />)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "when CSS options include 'style'" do
|
65
|
+
it "uses the 'style' to hide the fake input tag" do
|
66
|
+
subject.input(%w(user name), style: 'padding-top: 2px;').must_equal %(\
|
67
|
+
<input style="padding-top: 2px;" type="text" name="#{user_digest}[#{name_digest}]" />\
|
68
|
+
<input type="text" name="user[name]" style="#{hiding}" />)
|
38
69
|
end
|
39
70
|
end
|
40
71
|
end
|
41
72
|
|
42
|
-
describe
|
43
|
-
describe
|
73
|
+
describe '#submit' do
|
74
|
+
describe 'when type is mentioned' do
|
44
75
|
it "renders an input tag of specified 'submit" do
|
45
|
-
|
76
|
+
tag = %(<input type="submit" value="Submit" />)
|
77
|
+
subject.submit('Submit').must_equal tag
|
46
78
|
end
|
47
79
|
end
|
48
80
|
|
49
|
-
describe
|
50
|
-
it
|
51
|
-
subject.submit(
|
81
|
+
describe 'when CSS options are mentioned' do
|
82
|
+
it 'renders the options' do
|
83
|
+
subject.submit('Submit', id: 'submit', class: 'submit').must_equal %(\
|
84
|
+
<input id="submit" class="submit" type="submit" value="Submit" />)
|
52
85
|
end
|
53
86
|
end
|
54
87
|
end
|
55
88
|
|
56
|
-
describe
|
57
|
-
describe
|
58
|
-
it
|
59
|
-
subject.namify([
|
89
|
+
describe '#namify' do
|
90
|
+
describe 'when size is one' do
|
91
|
+
it 'is the word itself' do
|
92
|
+
subject.namify(['user']).must_equal 'user'
|
60
93
|
end
|
61
94
|
end
|
62
95
|
|
63
|
-
describe
|
64
|
-
it
|
65
|
-
subject.namify(
|
96
|
+
describe 'when size is more than one' do
|
97
|
+
it 'nests the items in successive square brackets' do
|
98
|
+
subject.namify(%w(user name first)).must_equal 'user[name][first]'
|
66
99
|
end
|
67
100
|
end
|
68
101
|
end
|
69
102
|
|
70
|
-
describe
|
71
|
-
it
|
103
|
+
describe '#decrypt' do
|
104
|
+
it 'fetches the actual value of the lookup up key' do
|
72
105
|
params = {
|
73
|
-
|
74
|
-
|
106
|
+
'user' => { 'name' => 'spam value' },
|
107
|
+
user_digest => { name_digest => 'true value' }
|
75
108
|
}
|
76
109
|
|
77
|
-
subject.decrypt(
|
110
|
+
subject.decrypt('user', params).must_equal('name' => 'true value')
|
78
111
|
end
|
79
112
|
|
80
113
|
describe "when lookup doesn't exist" do
|
81
|
-
it
|
114
|
+
it 'is empty' do
|
82
115
|
params = {
|
83
|
-
|
84
|
-
|
116
|
+
'user' => { 'name' => 'spam value' },
|
117
|
+
user_digest => { name_digest => 'true value' }
|
85
118
|
}
|
86
|
-
subject.decrypt(
|
119
|
+
subject.decrypt('missing_user', params).must_equal({})
|
87
120
|
end
|
88
121
|
end
|
89
122
|
|
90
123
|
describe "when fake keys don't conform to the decrypted keys" do
|
91
|
-
it
|
124
|
+
it 'populates the key with a nil value' do
|
92
125
|
params = {
|
93
|
-
|
94
|
-
|
126
|
+
'user' => { 'name' => 'spam value' },
|
127
|
+
user_digest.succ => { name_digest => 'true value' }
|
95
128
|
}
|
96
|
-
subject.decrypt(
|
129
|
+
subject.decrypt('user', params).must_equal('name' => nil)
|
97
130
|
end
|
98
131
|
end
|
99
132
|
end
|
100
133
|
|
101
|
-
describe
|
102
|
-
describe
|
103
|
-
it
|
134
|
+
describe '#valid?' do
|
135
|
+
describe 'when none of the paths under lookup are populated' do
|
136
|
+
it 'is true' do
|
104
137
|
params = {
|
105
|
-
|
106
|
-
|
138
|
+
'user' => { 'name' => '' },
|
139
|
+
user_digest => { name_digest => 'true value' }
|
107
140
|
}
|
108
141
|
|
109
|
-
subject.valid?(
|
142
|
+
subject.valid?('user', params).must_equal true
|
110
143
|
end
|
111
144
|
end
|
112
145
|
|
113
|
-
describe
|
114
|
-
it
|
146
|
+
describe 'when one of the paths under lookup is populated' do
|
147
|
+
it 'is false' do
|
115
148
|
params = {
|
116
|
-
|
117
|
-
|
149
|
+
'user' => { 'name' => 'spam value' },
|
150
|
+
user_digest => { name_digest => 'true value' }
|
118
151
|
}
|
119
152
|
|
120
|
-
subject.valid?(
|
153
|
+
subject.valid?('user', params).must_equal false
|
121
154
|
end
|
122
155
|
end
|
123
156
|
|
124
157
|
describe "when lookup doesn't exist" do
|
125
|
-
it
|
158
|
+
it 'is true' do
|
126
159
|
params = {
|
127
|
-
|
128
|
-
|
160
|
+
'user' => { 'name' => 'spam value' },
|
161
|
+
user_digest => { name_digest => 'true value' }
|
129
162
|
}
|
130
163
|
|
131
|
-
subject.valid?(
|
164
|
+
subject.valid?('user_missing', params).must_equal true
|
132
165
|
end
|
133
166
|
end
|
134
167
|
end
|
@@ -1,21 +1,21 @@
|
|
1
1
|
<html>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
<head>
|
3
|
+
<title>Sample Sinatra application</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<div id="result"><%= result %></div>
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
<form method="post" action="/">
|
9
|
+
<label for="user-first-name">First name</label>
|
10
|
+
<%= input ["user", "first_name"], :id => "user-first-name" %>
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
<label for="user-last-name">Last name</label>
|
13
|
+
<%= input ["user", "last_name"], :id => "user-last-name" %>
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
<label for="user-email">Email</label>
|
16
|
+
<%= input ["user", "email"], :size => 40, :id => "user-email" %>
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
<%= submit "Create account", :id => "user-submit" %>
|
19
|
+
</form>
|
20
|
+
</body>
|
21
21
|
</html>
|
data/spec/spec_helper.rb
CHANGED
@@ -1,16 +1,28 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# spec_helper.rb
|
2
|
+
#
|
3
|
+
# Author:: Chirantan Mitra
|
4
|
+
# Copyright:: Copyright (c) 2013-2016. All rights reserved
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
ENV['environment'] = 'test'
|
3
8
|
|
4
9
|
begin
|
5
|
-
require
|
10
|
+
require 'simplecov'
|
11
|
+
require 'coveralls'
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [
|
13
|
+
SimpleCov::Formatter::HTMLFormatter,
|
14
|
+
Coveralls::SimpleCov::Formatter
|
15
|
+
]
|
6
16
|
SimpleCov.start do
|
7
|
-
add_filter
|
17
|
+
add_filter '/spec|test|vendor/'
|
8
18
|
end
|
9
19
|
rescue LoadError
|
10
|
-
puts "\nPlease install simplecov to generate coverage report!\n\n"
|
20
|
+
puts "\nPlease install simplecov & coveralls to generate coverage report!\n\n"
|
11
21
|
end
|
12
22
|
|
13
|
-
gem
|
14
|
-
require "minitest/autorun"
|
23
|
+
gem 'minitest'
|
15
24
|
|
16
|
-
|
25
|
+
require 'minitest/autorun'
|
26
|
+
require 'minitest/spec'
|
27
|
+
require 'rack/test'
|
28
|
+
require 'sinatra/base'
|
metadata
CHANGED
@@ -1,123 +1,149 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spambust
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chirantan Mitra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rack-test
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: simplecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: coveralls
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rdoc
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
86
114
|
requirements:
|
87
|
-
- -
|
115
|
+
- - ">="
|
88
116
|
- !ruby/object:Gem::Version
|
89
117
|
version: '0'
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
|
-
- -
|
122
|
+
- - ">="
|
95
123
|
- !ruby/object:Gem::Version
|
96
124
|
version: '0'
|
97
|
-
description:
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
this and respond appropriately.
|
102
|
-
|
103
|
-
'
|
125
|
+
description: |
|
126
|
+
Render input tags with a masked name. The original input names names are
|
127
|
+
hidden. A spam bot would fill the incorrect but visible input tags. The server
|
128
|
+
would identify this and respond appropriately.
|
104
129
|
email:
|
105
130
|
- chirantan.mitra@gmail.com
|
106
131
|
executables: []
|
107
132
|
extensions: []
|
108
133
|
extra_rdoc_files: []
|
109
134
|
files:
|
135
|
+
- CHANGELOG.md
|
136
|
+
- LICENSE
|
137
|
+
- README.md
|
138
|
+
- lib/spambust.rb
|
110
139
|
- lib/spambust/form_helpers.rb
|
111
140
|
- lib/spambust/version.rb
|
112
|
-
- lib/spambust.rb
|
113
141
|
- spec/spambust/demo_app.rb
|
114
142
|
- spec/spambust/form_helpers_integration_spec.rb
|
115
143
|
- spec/spambust/form_helpers_spec.rb
|
116
144
|
- spec/spambust/views/index.erb
|
117
145
|
- spec/spambust/views/layout.erb
|
118
146
|
- spec/spec_helper.rb
|
119
|
-
- LICENSE
|
120
|
-
- README.md
|
121
147
|
homepage: https://github.com/chiku/spambust
|
122
148
|
licenses:
|
123
149
|
- MIT
|
@@ -128,17 +154,17 @@ require_paths:
|
|
128
154
|
- lib
|
129
155
|
required_ruby_version: !ruby/object:Gem::Requirement
|
130
156
|
requirements:
|
131
|
-
- -
|
157
|
+
- - ">="
|
132
158
|
- !ruby/object:Gem::Version
|
133
159
|
version: '0'
|
134
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
161
|
requirements:
|
136
|
-
- -
|
162
|
+
- - ">="
|
137
163
|
- !ruby/object:Gem::Version
|
138
164
|
version: '0'
|
139
165
|
requirements: []
|
140
166
|
rubyforge_project: spambust
|
141
|
-
rubygems_version: 2.1
|
167
|
+
rubygems_version: 2.5.1
|
142
168
|
signing_key:
|
143
169
|
specification_version: 4
|
144
170
|
summary: Sinatra form helper to reduce spams
|
@@ -146,6 +172,6 @@ test_files:
|
|
146
172
|
- spec/spambust/demo_app.rb
|
147
173
|
- spec/spambust/form_helpers_integration_spec.rb
|
148
174
|
- spec/spambust/form_helpers_spec.rb
|
149
|
-
- spec/spambust/views/index.erb
|
150
175
|
- spec/spambust/views/layout.erb
|
176
|
+
- spec/spambust/views/index.erb
|
151
177
|
- spec/spec_helper.rb
|