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 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
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :filter, :class => Stoolie::Filter do
3
+ client SmartFilter
4
+ end
5
+ end
@@ -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: []