stoolie 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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +102 -0
- data/LICENSE.txt +20 -0
- data/README.md +156 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/stoolie/clients/smart_filter.rb +157 -0
- data/lib/stoolie/filter.rb +29 -0
- data/lib/stoolie/railtie.rb +16 -0
- data/lib/stoolie/result.rb +51 -0
- data/lib/stoolie.rb +27 -0
- data/spec/factories.rb +5 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/stoolie/clients/smart_filter_spec.rb +123 -0
- data/spec/stoolie/filter_spec.rb +25 -0
- data/spec/stoolie/result_spec.rb +31 -0
- data/spec/stoolie_spec.rb +0 -0
- data/stoolie.gemspec +85 -0
- metadata +204 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca19f561e69ea9b59e6f99a52eac6c83060df20d
|
4
|
+
data.tar.gz: 37f3c585bd23ab5c67a3c3375978fe74bfb31827
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bef278518cb9933be6b5b145f0a4f0be41e9d7da55d329bdb04cb5742ad5fc1082fb272bbd427ae6f9282c8ce2316da374b46a4aec0b3a94bc4a09912f99dcc2
|
7
|
+
data.tar.gz: 2557aa46e4a39bde6736362f042d44e6bdb4e47958e3094ce320be1748e1a23699fcf7a42e633a17dbcfdef5bc351fcf47dc58b084b1ee09a6315397b2604409
|
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem 'loofah', '~> 1.2.1'
|
7
|
+
gem 'httparty', '~> 0.13.0'
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem 'rspec'
|
13
|
+
gem 'rspec-mocks'
|
14
|
+
gem 'factory_girl', '~> 4.0'
|
15
|
+
gem "shoulda", ">= 0"
|
16
|
+
gem "rdoc", "~> 3.12"
|
17
|
+
gem "bundler", "~> 1.0"
|
18
|
+
gem "jeweler", "~> 2.0.1"
|
19
|
+
gem "simplecov", ">= 0"
|
20
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.0.4)
|
5
|
+
i18n (~> 0.6, >= 0.6.9)
|
6
|
+
minitest (~> 4.2)
|
7
|
+
multi_json (~> 1.3)
|
8
|
+
thread_safe (~> 0.1)
|
9
|
+
tzinfo (~> 0.3.37)
|
10
|
+
addressable (2.3.5)
|
11
|
+
atomic (1.1.16)
|
12
|
+
builder (3.2.2)
|
13
|
+
descendants_tracker (0.0.3)
|
14
|
+
diff-lcs (1.2.5)
|
15
|
+
docile (1.1.3)
|
16
|
+
factory_girl (4.4.0)
|
17
|
+
activesupport (>= 3.0.0)
|
18
|
+
faraday (0.9.0)
|
19
|
+
multipart-post (>= 1.2, < 3)
|
20
|
+
git (1.2.6)
|
21
|
+
github_api (0.11.3)
|
22
|
+
addressable (~> 2.3)
|
23
|
+
descendants_tracker (~> 0.0.1)
|
24
|
+
faraday (~> 0.8, < 0.10)
|
25
|
+
hashie (>= 1.2)
|
26
|
+
multi_json (>= 1.7.5, < 2.0)
|
27
|
+
nokogiri (~> 1.6.0)
|
28
|
+
oauth2
|
29
|
+
hashie (2.0.5)
|
30
|
+
highline (1.6.21)
|
31
|
+
httparty (0.13.0)
|
32
|
+
json (~> 1.8)
|
33
|
+
multi_xml (>= 0.5.2)
|
34
|
+
i18n (0.6.9)
|
35
|
+
jeweler (2.0.1)
|
36
|
+
builder
|
37
|
+
bundler (>= 1.0)
|
38
|
+
git (>= 1.2.5)
|
39
|
+
github_api
|
40
|
+
highline (>= 1.6.15)
|
41
|
+
nokogiri (>= 1.5.10)
|
42
|
+
rake
|
43
|
+
rdoc
|
44
|
+
json (1.8.1)
|
45
|
+
jwt (0.1.11)
|
46
|
+
multi_json (>= 1.5)
|
47
|
+
loofah (1.2.1)
|
48
|
+
nokogiri (>= 1.4.4)
|
49
|
+
mini_portile (0.5.2)
|
50
|
+
minitest (4.7.5)
|
51
|
+
multi_json (1.9.2)
|
52
|
+
multi_xml (0.5.5)
|
53
|
+
multipart-post (2.0.0)
|
54
|
+
nokogiri (1.6.1)
|
55
|
+
mini_portile (~> 0.5.0)
|
56
|
+
oauth2 (0.9.3)
|
57
|
+
faraday (>= 0.8, < 0.10)
|
58
|
+
jwt (~> 0.1.8)
|
59
|
+
multi_json (~> 1.3)
|
60
|
+
multi_xml (~> 0.5)
|
61
|
+
rack (~> 1.2)
|
62
|
+
rack (1.5.2)
|
63
|
+
rake (10.1.1)
|
64
|
+
rdoc (3.12.2)
|
65
|
+
json (~> 1.4)
|
66
|
+
rspec (2.14.1)
|
67
|
+
rspec-core (~> 2.14.0)
|
68
|
+
rspec-expectations (~> 2.14.0)
|
69
|
+
rspec-mocks (~> 2.14.0)
|
70
|
+
rspec-core (2.14.8)
|
71
|
+
rspec-expectations (2.14.5)
|
72
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
73
|
+
rspec-mocks (2.14.6)
|
74
|
+
shoulda (3.5.0)
|
75
|
+
shoulda-context (~> 1.0, >= 1.0.1)
|
76
|
+
shoulda-matchers (>= 1.4.1, < 3.0)
|
77
|
+
shoulda-context (1.1.6)
|
78
|
+
shoulda-matchers (2.5.0)
|
79
|
+
activesupport (>= 3.0.0)
|
80
|
+
simplecov (0.8.2)
|
81
|
+
docile (~> 1.1.0)
|
82
|
+
multi_json
|
83
|
+
simplecov-html (~> 0.8.0)
|
84
|
+
simplecov-html (0.8.0)
|
85
|
+
thread_safe (0.2.0)
|
86
|
+
atomic (>= 1.1.7, < 2)
|
87
|
+
tzinfo (0.3.39)
|
88
|
+
|
89
|
+
PLATFORMS
|
90
|
+
ruby
|
91
|
+
|
92
|
+
DEPENDENCIES
|
93
|
+
bundler (~> 1.0)
|
94
|
+
factory_girl (~> 4.0)
|
95
|
+
httparty (~> 0.13.0)
|
96
|
+
jeweler (~> 2.0.1)
|
97
|
+
loofah (~> 1.2.1)
|
98
|
+
rdoc (~> 3.12)
|
99
|
+
rspec
|
100
|
+
rspec-mocks
|
101
|
+
shoulda
|
102
|
+
simplecov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Aaron Wallis
|
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.md
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# stoolie
|
2
|
+
|
3
|
+
Content filter to determine the XSS, spam and profanity content of text
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
The easiest way to install is with [Bundler](http://gembundler.com)
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'stoolie'
|
11
|
+
```
|
12
|
+
|
13
|
+
Stoolie will work on Ruby 1.8.7+
|
14
|
+
|
15
|
+
## Configuration
|
16
|
+
|
17
|
+
In Rails, create an initializer such as `config/initializers/stoolie.rb` and add your SmartFilter rule key and API key
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
MyApplication::Application.config.stoolie.smart_filter.api_key = 'my api key'
|
21
|
+
MyApplication::Application.config.stoolie.smart_filter.rule_key = 'my rule key'
|
22
|
+
```
|
23
|
+
|
24
|
+
Otherwise, add the keys like so:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
Stoolie.configure do |config|
|
28
|
+
config.smart_filter = {rule_key: 'rule-key', api_key: 'api-key'}
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
### Default Filter and SmartFilter Thresholds
|
33
|
+
|
34
|
+
Stoolie uses [Prevoty's SmartFilter](http://www.isodsgn.com/source/work/pv/v1_site/smartfilter.htm) by default, but it's possible to [add](#extending-stoolie) other clients as long as they implement stoolie's API requirements.
|
35
|
+
|
36
|
+
These are the default thresholds.
|
37
|
+
|
38
|
+
#### XSS
|
39
|
+
|
40
|
+
* javascript_threshold - 1
|
41
|
+
* invalid_tags_threshold - 1
|
42
|
+
|
43
|
+
#### Spam
|
44
|
+
|
45
|
+
* link_density_threshold - 3
|
46
|
+
* spam_features_threshold - 2
|
47
|
+
|
48
|
+
#### Blacklisted Phrases
|
49
|
+
|
50
|
+
* blacklisted_phrases_threshold - 1
|
51
|
+
|
52
|
+
#### Offensive Phrases
|
53
|
+
|
54
|
+
* flagged_phrases_threshold - 6
|
55
|
+
* profanity_threshold - 3
|
56
|
+
|
57
|
+
All thresholds can be configured using the names given above.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
MyApplication.config.stoolie.smart_filter.profanity_threshold = 5
|
61
|
+
```
|
62
|
+
|
63
|
+
## Examples
|
64
|
+
|
65
|
+
### XSS
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
> result = Stoolie::Filter.new.analyze('<script>xss is bad.</script>')
|
69
|
+
=> #<Stoolie::Result>
|
70
|
+
> result.is_insecure?
|
71
|
+
=> true
|
72
|
+
```
|
73
|
+
|
74
|
+
### Spam
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
> result = Stoolie::Filter.new.analyze('http://mylink.com http://anotherlink.com http://yetanotherlink.com')
|
78
|
+
=> #<Stoolie::Result>
|
79
|
+
> result.is_spam?
|
80
|
+
=> true
|
81
|
+
```
|
82
|
+
|
83
|
+
### Blacklisted Phrases
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
> filter = Stoolie::Filter.new
|
87
|
+
> result = filter.analyze('some text')
|
88
|
+
=> #<Stoolie::Result>
|
89
|
+
> result.is_blacklisted?
|
90
|
+
=> false
|
91
|
+
> result = filter.analyze('an incredibly racist word')
|
92
|
+
=> #<Stoolie::Result>
|
93
|
+
> result.is_blacklisted?
|
94
|
+
=> true
|
95
|
+
```
|
96
|
+
|
97
|
+
### Offensive Phrases
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
> result = Stoolie::Filter.new.analyze('enough curse words to trip the threshold')
|
101
|
+
=> #<Stoolie::Result>
|
102
|
+
> result.is_offensive?
|
103
|
+
=> true
|
104
|
+
```
|
105
|
+
|
106
|
+
## Extending stoolie
|
107
|
+
|
108
|
+
If you want to add your own filter client, create one in `lib/stoolie/clients/` and make sure it meets the API requirements:
|
109
|
+
|
110
|
+
### Instance Attributes
|
111
|
+
|
112
|
+
* input
|
113
|
+
* output
|
114
|
+
|
115
|
+
### Implement public `analyze` instance method
|
116
|
+
|
117
|
+
* Should accept a string argument
|
118
|
+
* Should return a Stoolie::Result object, which takes the client instance as its argument
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
return @result = Stoolie::Result.new(self)
|
122
|
+
```
|
123
|
+
|
124
|
+
### Implement these public boolean instance methods
|
125
|
+
|
126
|
+
* is_insecure?
|
127
|
+
* is_spam?
|
128
|
+
* is_blacklisted?
|
129
|
+
* is_offensive?
|
130
|
+
|
131
|
+
To use your new client instead of SmartFilter, you can set it in your configuration:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
MyApplication::Application.config.client = MyFilterClient
|
135
|
+
```
|
136
|
+
|
137
|
+
Or on the fly
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
filter = Stoolie::Filter.new(MyFilterClient)
|
141
|
+
```
|
142
|
+
|
143
|
+
## Contributing to stoolie
|
144
|
+
|
145
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
146
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
147
|
+
* Fork the project.
|
148
|
+
* Start a feature/bugfix branch.
|
149
|
+
* Commit and push until you are happy with your contribution.
|
150
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
151
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
152
|
+
|
153
|
+
## Copyright
|
154
|
+
|
155
|
+
Copyright (c) 2014 Aaron Wallis. See LICENSE.txt for
|
156
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "stoolie"
|
18
|
+
gem.homepage = "http://github.com/wheels/stoolie"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Content filter to determine the XSS, spam or offensive quality of text}
|
21
|
+
gem.description = %Q{Content filter to determine the XSS, spam or offensive quality of text.}
|
22
|
+
gem.email = "awallis@bleacherreport.com"
|
23
|
+
gem.authors = ["Aaron Wallis"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Code coverage detail"
|
36
|
+
task :simplecov do
|
37
|
+
ENV['COVERAGE'] = "true"
|
38
|
+
Rake::Task['test'].execute
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :test
|
42
|
+
|
43
|
+
require 'rdoc/task'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "stoolie #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.2
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../result')
|
2
|
+
require 'yaml'
|
3
|
+
require 'httparty'
|
4
|
+
|
5
|
+
class SmartFilterNetworkException < Exception; end
|
6
|
+
class SmartFilterBadInputParameter < Exception; end
|
7
|
+
class SmartFilterBadAPIKey < Exception; end
|
8
|
+
class SmartFilterRequestTooLarge < Exception; end
|
9
|
+
class SmartFilterInternalError < Exception; end
|
10
|
+
class SmartFilterAccountQuotaExceeded < Exception; end
|
11
|
+
|
12
|
+
class SmartFilter
|
13
|
+
attr_accessor :rule_key, :api_key, :base
|
14
|
+
attr_accessor :input, :output
|
15
|
+
|
16
|
+
def initialize(opts = {})
|
17
|
+
@config = SmartFilter.config(Stoolie.config.smart_filter)
|
18
|
+
|
19
|
+
@rule_key = opts[:rule_key] || @config[:rule_key]
|
20
|
+
@api_key = @config[:api_key]
|
21
|
+
|
22
|
+
@base = 'https://api.prevoty.com/1'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Endpoint: /xss/filter
|
26
|
+
def filter(input, rule_key)
|
27
|
+
begin
|
28
|
+
return filter!(input, rule_key)
|
29
|
+
rescue => e
|
30
|
+
return nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter!(input, rule_key = nil)
|
35
|
+
rule_key ||= @rule_key
|
36
|
+
options = {:api_key => @api_key, :input => input, :rule_key => rule_key}
|
37
|
+
response = HTTParty.post("#{@base}/xss/filter", :query => options, :timeout => 5)
|
38
|
+
|
39
|
+
unless rule_key and @api_key
|
40
|
+
raise "You must configure Smart Filter to use a rule key and api key"
|
41
|
+
end
|
42
|
+
|
43
|
+
return JSON.parse(response.body) if response.code == 200
|
44
|
+
raise SmartFilterBadInputParameter if response.code == 400
|
45
|
+
raise SmartFilterBadAPIKey if response.code == 403
|
46
|
+
raise SmartFilterRequestTooLarge if response.code == 413
|
47
|
+
raise SmartFilterInternalError if response.code == 500
|
48
|
+
raise SmartFilterAccountQuotaExceeded if response.code == 507
|
49
|
+
Array.new
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: allows changing the thresholds at which the xss, spam & offensive methods will trigger
|
53
|
+
#
|
54
|
+
# Returns a hash
|
55
|
+
def self.config(opts = {})
|
56
|
+
opts[:blacklisted_phrases_threshold] ||= 1
|
57
|
+
opts[:flagged_phrases_threshold] ||= 6
|
58
|
+
opts[:profanity_threshold] ||= 3
|
59
|
+
opts[:link_density_threshold] ||= 3
|
60
|
+
opts[:spam_features_threshold] ||= 2
|
61
|
+
opts[:javascript_threshold] ||= 1
|
62
|
+
opts[:invalid_tags_threshold] ||= 1
|
63
|
+
opts
|
64
|
+
end
|
65
|
+
|
66
|
+
# Public: retrieves the threshold (configured or default) for a given statistic
|
67
|
+
#
|
68
|
+
# Returns a hash of the form {'statistic' => ${threshold value}}
|
69
|
+
def self.stats_to_thresholds
|
70
|
+
map = {}
|
71
|
+
map['blacklisted_phrases'] = config[:blacklisted_phrases_threshold]
|
72
|
+
map['flagged_phrases'] = config[:flagged_phrases_threshold]
|
73
|
+
map['prevoty_profanity_features'] = config[:profanity_threshold]
|
74
|
+
map['prevoty_link_density'] = config[:link_density_threshold]
|
75
|
+
map['prevoty_spam_features'] = config[:spam_features_threshold]
|
76
|
+
|
77
|
+
['invalid_protocols', 'invalid_attributes', 'invalid_tags'].each do |s|
|
78
|
+
map[s] = config[:invalid_tags_threshold]
|
79
|
+
end
|
80
|
+
['javascript_tags', 'javascript_protocols', 'javascript_attributes'].each do |s|
|
81
|
+
map[s] = config[:javascript_threshold]
|
82
|
+
end
|
83
|
+
|
84
|
+
map
|
85
|
+
end
|
86
|
+
|
87
|
+
def stat_to_threshold(stat)
|
88
|
+
SmartFilter.stats_to_thresholds[stat.to_s]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: calls the filter method to test the input
|
92
|
+
#
|
93
|
+
# input - the text to be analyzed
|
94
|
+
#
|
95
|
+
# Examples
|
96
|
+
#
|
97
|
+
# result = analyze('<script>text</script>')
|
98
|
+
# # => #<Stoolie::Result...>
|
99
|
+
# result.insecure?
|
100
|
+
# # => true
|
101
|
+
#
|
102
|
+
# Returns a Stoolie::Result object
|
103
|
+
def analyze(input = '')
|
104
|
+
@input = input
|
105
|
+
result = filter!(@input)
|
106
|
+
@stats = result['statistics']
|
107
|
+
@output = result['output']
|
108
|
+
@result = Stoolie::Result.new(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Public: checks the result statistics against the thresholds for security
|
112
|
+
#
|
113
|
+
# Returns a boolean
|
114
|
+
def is_insecure?
|
115
|
+
return false unless @stats
|
116
|
+
|
117
|
+
beyond_threshold?(['javascript_tags', 'javascript_protocols', 'javascript_attributes', 'invalid_protocols', 'invalid_attributes'])
|
118
|
+
end
|
119
|
+
|
120
|
+
# Public: checks the result statistics against the thresholds for spam
|
121
|
+
#
|
122
|
+
# Returns a boolean
|
123
|
+
def is_spam?
|
124
|
+
return false unless @stats
|
125
|
+
|
126
|
+
beyond_threshold?(['prevoty_spam_features']) || link_density(@stats['prevoty_link_density']) >= stat_to_threshold('prevoty_link_density')
|
127
|
+
end
|
128
|
+
|
129
|
+
# Public: checks the result statistics against the thresholds for blacklisted phrases
|
130
|
+
#
|
131
|
+
# Returns a boolean
|
132
|
+
def is_blacklisted?
|
133
|
+
return false unless @stats
|
134
|
+
|
135
|
+
beyond_threshold?(['blacklisted_phrases'])
|
136
|
+
end
|
137
|
+
|
138
|
+
# Public: checks the result statistics against the thresholds for profanity
|
139
|
+
#
|
140
|
+
# Returns a boolean
|
141
|
+
def is_offensive?
|
142
|
+
return false unless @stats
|
143
|
+
|
144
|
+
beyond_threshold?(['flagged_phrases', 'prevoty_profanity_features'])
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def link_density(links = {})
|
150
|
+
links.values.inject(&:+) || 0
|
151
|
+
end
|
152
|
+
|
153
|
+
def beyond_threshold?(stats = [])
|
154
|
+
stats.any? { |stat| @stats[stat] >= stat_to_threshold(stat) }
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/clients/smart_filter')
|
2
|
+
|
3
|
+
module Stoolie
|
4
|
+
class Filter
|
5
|
+
attr_accessor :client, :result
|
6
|
+
|
7
|
+
# Public: takes the class name of the filter client to use - e.g. SmartFilter
|
8
|
+
def initialize(client = nil)
|
9
|
+
@config = Stoolie.config
|
10
|
+
|
11
|
+
@client = client || Stoolie.config.client
|
12
|
+
@client = @client.send(:new)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: analyzes a string for xss, spam and profanity
|
16
|
+
#
|
17
|
+
# input - the string to be analyzed
|
18
|
+
#
|
19
|
+
# Examples:
|
20
|
+
#
|
21
|
+
# analyze('<script>text</script>')
|
22
|
+
# # => #<Stoolie::Result: ...>
|
23
|
+
#
|
24
|
+
# Returns a Stoolie::Result object
|
25
|
+
def analyze(input)
|
26
|
+
@result = @client.analyze(input)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../stoolie')
|
2
|
+
|
3
|
+
module Stoolie
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
|
6
|
+
config.stoolie = ActiveSupport::OrderedOptions.new
|
7
|
+
config.stoolie.smart_filter = ActiveSupport::OrderedOptions.new
|
8
|
+
|
9
|
+
initializer "stoolie" do |app|
|
10
|
+
Stoolie.configure do |config|
|
11
|
+
config.client = app.config.stoolie.client if app.config.stoolie.client
|
12
|
+
config.smart_filter = app.config.stoolie.smart_filter
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Stoolie
|
2
|
+
class Result
|
3
|
+
attr_accessor :client, :input, :output
|
4
|
+
|
5
|
+
# Client must have the following attributes:
|
6
|
+
#
|
7
|
+
# #input
|
8
|
+
# #output
|
9
|
+
#
|
10
|
+
# Client must implement the following methods
|
11
|
+
#
|
12
|
+
# #analyze(input)
|
13
|
+
# #is_insecure?
|
14
|
+
# #is_spammy?
|
15
|
+
# #is_blacklisted?
|
16
|
+
# #is_offensive?
|
17
|
+
def initialize(client)
|
18
|
+
@client = client
|
19
|
+
@input = client.input
|
20
|
+
@ouput = client.output
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: tells whether the client found XSS
|
24
|
+
#
|
25
|
+
# Returns a boolean
|
26
|
+
def is_insecure?
|
27
|
+
@client.is_insecure?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: tells whether the client found spam
|
31
|
+
#
|
32
|
+
# Returns a boolean
|
33
|
+
def is_spam?
|
34
|
+
@client.is_spam?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: tells whether the client found blacklisted phrases
|
38
|
+
#
|
39
|
+
# Returns a boolean
|
40
|
+
def is_blacklisted?
|
41
|
+
@client.is_blacklisted?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: tells whether the client found offensive words or phrases
|
45
|
+
#
|
46
|
+
# Returns a boolean
|
47
|
+
def is_offensive?
|
48
|
+
@client.is_offensive?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/stoolie.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Stoolie
|
2
|
+
VERSION = "0.0.2"
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :config
|
6
|
+
|
7
|
+
def configure
|
8
|
+
@config ||= Config.new
|
9
|
+
yield(config) if block_given?
|
10
|
+
@config
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Config
|
15
|
+
attr_accessor :client, :smart_filter
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@client = SmartFilter
|
19
|
+
@smart_filter = {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
require File.expand_path(File.dirname(__FILE__) + '/stoolie/filter')
|
26
|
+
|
27
|
+
require 'stoolie/railtie' if defined?(Rails::Railtie)
|
data/spec/factories.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/stoolie')
|
2
|
+
require 'factory_girl'
|
3
|
+
|
4
|
+
include RSpec::Mocks::ExampleMethods
|
5
|
+
|
6
|
+
FactoryGirl.definition_file_paths = %w(../factories)
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.include FactoryGirl::Syntax::Methods
|
10
|
+
|
11
|
+
config.before(:each) do
|
12
|
+
Stoolie.configure do |config|
|
13
|
+
config.smart_filter = {api_key: 'api-key', rule_key: 'rule-key'}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
config.before(:each) do
|
18
|
+
def stub_filter(stats = {})
|
19
|
+
filter_result = {"message"=>"", "output"=>"", "statistics"=>{"bytes"=>4, "invalid_attributes"=>0,
|
20
|
+
"invalid_protocols"=>0, "invalid_tags"=>0, "blacklisted_phrases"=>0, "flagged_phrases"=>0,
|
21
|
+
"javascript_attributes"=>0, "javascript_protocols"=>0, "javascript_tags"=>0,
|
22
|
+
"prevoty_profanity_features"=>0, "prevoty_spam_features"=>0, "prevoty_link_metadata"=>{},
|
23
|
+
"prevoty_link_density"=>{}, "tags_balanced"=>0, "transformations"=>0}}
|
24
|
+
filter_result['statistics'].merge!(stats)
|
25
|
+
SmartFilter.any_instance.stub(:filter!).and_return(filter_result)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SmartFilter do
|
4
|
+
before(:each) do
|
5
|
+
stub_filter
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.analyze' do
|
9
|
+
subject { SmartFilter.new }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
subject.analyze('some text')
|
13
|
+
end
|
14
|
+
|
15
|
+
it { subject.instance_variable_get(:@input).should_not be_nil }
|
16
|
+
it { subject.instance_variable_get(:@stats).should_not be_nil }
|
17
|
+
it { subject.instance_variable_get(:@output).should_not be_nil }
|
18
|
+
it { subject.instance_variable_get(:@result).should_not be_nil }
|
19
|
+
it { subject.instance_variable_get(:@result).should be_a(Stoolie::Result) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'with benign text' do
|
23
|
+
subject { SmartFilter.new }
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
stub_filter
|
27
|
+
subject.analyze('benign text')
|
28
|
+
end
|
29
|
+
|
30
|
+
it { subject.should_not be_is_insecure }
|
31
|
+
it { subject.should_not be_is_spam }
|
32
|
+
it { subject.should_not be_is_offensive }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.is_insecure? with malicious text exceeding the javascript threshold' do
|
36
|
+
subject { SmartFilter.new }
|
37
|
+
|
38
|
+
context 'because of tags' do
|
39
|
+
before(:each) do
|
40
|
+
stub_filter('javascript_tags' => 1)
|
41
|
+
subject.analyze('<script>malicious text</script>')
|
42
|
+
end
|
43
|
+
|
44
|
+
it { subject.should be_is_insecure }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'because of protocols' do
|
48
|
+
before(:each) do
|
49
|
+
stub_filter('javascript_protocols' => 1)
|
50
|
+
subject.analyze('<script>malicious text</script>')
|
51
|
+
end
|
52
|
+
|
53
|
+
it { subject.should be_is_insecure }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'because of attributes' do
|
57
|
+
before(:each) do
|
58
|
+
stub_filter('javascript_attributes' => 1)
|
59
|
+
subject.analyze('<script>malicious text</script>')
|
60
|
+
end
|
61
|
+
|
62
|
+
it { subject.should be_is_insecure }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.is_spam? with malicious text exceeding the spam threshold' do
|
67
|
+
subject { SmartFilter.new }
|
68
|
+
|
69
|
+
context 'because of link density' do
|
70
|
+
before(:each) do
|
71
|
+
stub_filter('prevoty_link_density' => {'http://test.com' => 2, 'http://alsotest.com' => 1})
|
72
|
+
subject.analyze('spammy text')
|
73
|
+
end
|
74
|
+
|
75
|
+
it { subject.should be_is_spam }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'because of spam features' do
|
79
|
+
before(:each) do
|
80
|
+
stub_filter('prevoty_spam_features' => 2)
|
81
|
+
subject.analyze('spammy text')
|
82
|
+
end
|
83
|
+
|
84
|
+
it { subject.should be_is_spam }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '.is_blacklisted? with malicious text exceeding the blacklist threshold' do
|
89
|
+
subject { SmartFilter.new }
|
90
|
+
|
91
|
+
context 'because of blacklisted phrases' do
|
92
|
+
before(:each) do
|
93
|
+
stub_filter('blacklisted_phrases' => 1)
|
94
|
+
subject.analyze('offensive text')
|
95
|
+
end
|
96
|
+
|
97
|
+
it { subject.should be_is_blacklisted }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '.is_offensive? with malicious text exceeding the offensive threshold' do
|
102
|
+
subject { SmartFilter.new }
|
103
|
+
|
104
|
+
context 'because of flagged phrases' do
|
105
|
+
before(:each) do
|
106
|
+
stub_filter('flagged_phrases' => 6)
|
107
|
+
subject.analyze('offensive text')
|
108
|
+
end
|
109
|
+
|
110
|
+
it { subject.should be_is_offensive }
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'because of profanity' do
|
114
|
+
before(:each) do
|
115
|
+
stub_filter('prevoty_profanity_features' => 3)
|
116
|
+
subject.analyze('offensive text')
|
117
|
+
end
|
118
|
+
|
119
|
+
it { subject.should be_is_offensive }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stoolie::Filter do
|
4
|
+
|
5
|
+
subject { Stoolie::Filter.new }
|
6
|
+
|
7
|
+
describe "#new" do
|
8
|
+
it { should respond_to(:client) }
|
9
|
+
it { subject.client.should be_a(SmartFilter) }
|
10
|
+
|
11
|
+
it { should respond_to(:result) }
|
12
|
+
it { subject.result.should be_nil }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#analyze" do
|
16
|
+
before(:each) do
|
17
|
+
stub_filter
|
18
|
+
end
|
19
|
+
|
20
|
+
subject { Stoolie::Filter.new.analyze('some text') }
|
21
|
+
|
22
|
+
it { subject.should be_a(Stoolie::Result) }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Stoolie::Result do
|
4
|
+
|
5
|
+
subject { Stoolie::Result.new(SmartFilter.new) }
|
6
|
+
|
7
|
+
describe "#new" do
|
8
|
+
it { should respond_to(:client) }
|
9
|
+
it { subject.client.should be_a(SmartFilter) }
|
10
|
+
|
11
|
+
it { should respond_to(:input) }
|
12
|
+
it { should respond_to(:output) }
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#is_insecure?" do
|
16
|
+
it { should respond_to(:is_insecure?) }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#is_spam?" do
|
20
|
+
it { should respond_to(:is_spam?) }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#is_blacklisted?" do
|
24
|
+
it { should respond_to(:is_blacklisted?) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#is_offensive?" do
|
28
|
+
it { should respond_to(:is_offensive?) }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
File without changes
|
data/stoolie.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "stoolie"
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Aaron Wallis"]
|
12
|
+
s.date = "2014-06-06"
|
13
|
+
s.description = "Content filter to determine the XSS, spam or offensive quality of text."
|
14
|
+
s.email = "awallis@bleacherreport.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.md",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/stoolie.rb",
|
28
|
+
"lib/stoolie/clients/smart_filter.rb",
|
29
|
+
"lib/stoolie/filter.rb",
|
30
|
+
"lib/stoolie/railtie.rb",
|
31
|
+
"lib/stoolie/result.rb",
|
32
|
+
"spec/factories.rb",
|
33
|
+
"spec/spec_helper.rb",
|
34
|
+
"spec/stoolie/clients/smart_filter_spec.rb",
|
35
|
+
"spec/stoolie/filter_spec.rb",
|
36
|
+
"spec/stoolie/result_spec.rb",
|
37
|
+
"spec/stoolie_spec.rb",
|
38
|
+
"stoolie.gemspec"
|
39
|
+
]
|
40
|
+
s.homepage = "http://github.com/wheels/stoolie"
|
41
|
+
s.licenses = ["MIT"]
|
42
|
+
s.require_paths = ["lib"]
|
43
|
+
s.rubygems_version = "2.0.14"
|
44
|
+
s.summary = "Content filter to determine the XSS, spam or offensive quality of text"
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
s.specification_version = 4
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<loofah>, ["~> 1.2.1"])
|
51
|
+
s.add_runtime_dependency(%q<httparty>, ["~> 0.13.0"])
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<rspec-mocks>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<factory_girl>, ["~> 4.0"])
|
55
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
57
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
58
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
59
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<loofah>, ["~> 1.2.1"])
|
62
|
+
s.add_dependency(%q<httparty>, ["~> 0.13.0"])
|
63
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
64
|
+
s.add_dependency(%q<rspec-mocks>, [">= 0"])
|
65
|
+
s.add_dependency(%q<factory_girl>, ["~> 4.0"])
|
66
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
67
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
68
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
69
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
70
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<loofah>, ["~> 1.2.1"])
|
74
|
+
s.add_dependency(%q<httparty>, ["~> 0.13.0"])
|
75
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
76
|
+
s.add_dependency(%q<rspec-mocks>, [">= 0"])
|
77
|
+
s.add_dependency(%q<factory_girl>, ["~> 4.0"])
|
78
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
79
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
80
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
81
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
82
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
metadata
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stoolie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aaron Wallis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: loofah
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.13.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.13.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: factory_girl
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: shoulda
|
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: rdoc
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.12'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.12'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: jeweler
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.0.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.0.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '>='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: Content filter to determine the XSS, spam or offensive quality of text.
|
154
|
+
email: awallis@bleacherreport.com
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files:
|
158
|
+
- LICENSE.txt
|
159
|
+
- README.md
|
160
|
+
files:
|
161
|
+
- .document
|
162
|
+
- Gemfile
|
163
|
+
- Gemfile.lock
|
164
|
+
- LICENSE.txt
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- VERSION
|
168
|
+
- lib/stoolie.rb
|
169
|
+
- lib/stoolie/clients/smart_filter.rb
|
170
|
+
- lib/stoolie/filter.rb
|
171
|
+
- lib/stoolie/railtie.rb
|
172
|
+
- lib/stoolie/result.rb
|
173
|
+
- spec/factories.rb
|
174
|
+
- spec/spec_helper.rb
|
175
|
+
- spec/stoolie/clients/smart_filter_spec.rb
|
176
|
+
- spec/stoolie/filter_spec.rb
|
177
|
+
- spec/stoolie/result_spec.rb
|
178
|
+
- spec/stoolie_spec.rb
|
179
|
+
- stoolie.gemspec
|
180
|
+
homepage: http://github.com/wheels/stoolie
|
181
|
+
licenses:
|
182
|
+
- MIT
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - '>='
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 2.0.14
|
201
|
+
signing_key:
|
202
|
+
specification_version: 4
|
203
|
+
summary: Content filter to determine the XSS, spam or offensive quality of text
|
204
|
+
test_files: []
|