obscenity-plus 1.0.1

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.
@@ -0,0 +1,19 @@
1
+ #active_model.rb
2
+ if defined?(ActiveModel)
3
+ module ActiveModel
4
+ module Validations
5
+ class ObscenityValidator < ActiveModel::EachValidator
6
+
7
+ def validate_each(record, attribute, value)
8
+ if options.present? && options.has_key?(:sanitize)
9
+ object = record.respond_to?(:[]) ? record[attribute] : record.send(attribute)
10
+ object = Obscenity.replacement(options[:replacement]).sanitize(object)
11
+ else
12
+ record.errors.add(attribute, options[:message] || 'cannot be profane') if Obscenity.profane?(value)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,75 @@
1
+ #base.rb
2
+ module Obscenity
3
+ class Base
4
+ class << self
5
+
6
+ def blacklist
7
+ @blacklist ||= set_list_content(Obscenity.config.blacklist)
8
+ end
9
+
10
+ def blacklist=(value)
11
+ @blacklist = value == :default ? set_list_content(Obscenity::Config.new.blacklist) : value
12
+ end
13
+
14
+ def whitelist
15
+ @whitelist ||= set_list_content(Obscenity.config.whitelist)
16
+ end
17
+
18
+ def whitelist=(value)
19
+ @whitelist = value == :default ? set_list_content(Obscenity::Config.new.whitelist) : value
20
+ end
21
+
22
+ def profane?(text)
23
+ return(false) unless text.to_s.size >= 3
24
+ blacklist.each do |foul|
25
+ return(true) if text =~ /\b#{foul}(?!\w)/i && !whitelist.include?(foul)
26
+ end
27
+ false
28
+ end
29
+
30
+ def sanitize(text)
31
+ return(text) unless text.to_s.size >= 3
32
+ blacklist.each do |foul|
33
+ text.gsub!(/\b#{foul}\b/i, replace(foul)) unless whitelist.include?(foul)
34
+ end
35
+ @scoped_replacement = nil
36
+ text
37
+ end
38
+
39
+ def replacement(chars)
40
+ @scoped_replacement = chars
41
+ self
42
+ end
43
+
44
+ def offensive(text)
45
+ words = []
46
+ return(words) unless text.to_s.size >= 3
47
+ blacklist.each do |foul|
48
+ words << foul if text =~ /\b#{foul}\b/i && !whitelist.include?(foul)
49
+ end
50
+ words.uniq
51
+ end
52
+
53
+ def replace(word)
54
+ content = @scoped_replacement || Obscenity.config.replacement
55
+ case content
56
+ when :vowels then word.gsub(/[aeiou]/i, '*')
57
+ when :stars then '*' * word.size
58
+ when :nonconsonants then word.gsub(/[^bcdfghjklmnpqrstvwxyz]/i, '*')
59
+ when :default, :garbled then '$@!#%'
60
+ else content
61
+ end
62
+ end
63
+
64
+ private
65
+ def set_list_content(list)
66
+ case list
67
+ when Array then list
68
+ when String, Pathname then YAML.load_file( list.to_s )
69
+ else []
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
1
+ module Obscenity
2
+ class Config
3
+
4
+ attr_accessor :replacement
5
+
6
+ DEFAULT_WHITELIST = []
7
+ DEFAULT_BLACKLIST = File.dirname(__FILE__) + "/../../config/blacklist.yml"
8
+
9
+ def initialize
10
+ yield(self) if block_given?
11
+ validate_config_options
12
+ end
13
+
14
+ def replacement
15
+ @replacement ||= :garbled
16
+ end
17
+
18
+ def blacklist
19
+ @blacklist ||= DEFAULT_BLACKLIST
20
+ end
21
+
22
+ def blacklist=(value)
23
+ @blacklist = value == :default ? DEFAULT_BLACKLIST : value
24
+ end
25
+
26
+ def whitelist
27
+ @whitelist ||= DEFAULT_WHITELIST
28
+ end
29
+
30
+ def whitelist=(value)
31
+ @whitelist = value == :default ? DEFAULT_WHITELIST : value
32
+ end
33
+
34
+ private
35
+ def validate_config_options
36
+ [@blacklist, @whitelist].each{ |content| validate_list_content(content) if content }
37
+ end
38
+
39
+ def validate_list_content(content)
40
+ case content
41
+ when Array then !content.empty? || raise(Obscenity::EmptyContentList.new('Content array is empty.'))
42
+ when String then File.exist?(content) || raise(Obscenity::UnkownContentFile.new("Content file can't be found."))
43
+ when Pathname then content.exist? || raise(Obscenity::UnkownContentFile.new("Content file can't be found."))
44
+ when Symbol then content == :default || raise(Obscenity::UnkownContent.new("The only accepted symbol is :default."))
45
+ else
46
+ raise Obscenity::UnkownContent.new("The content can be either an Array, Pathname, or String path to a .yml file.")
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ module Obscenity
2
+ class Error < RuntimeError; end
3
+
4
+ class UnkownContent < Error; end
5
+ class UnkownContentFile < Error; end
6
+ class EmptyContentList < Error; end
7
+ end
@@ -0,0 +1,89 @@
1
+ module Rack
2
+ class Obscenity
3
+
4
+ def initialize(app, options = {})
5
+ @app, @options = app, options
6
+ end
7
+
8
+ def call(env)
9
+ rejectable = false
10
+ post_params = Rack::Utils.parse_query(env['rack.input'].read, "&")
11
+ get_params = Rack::Utils.parse_query(env['QUERY_STRING'], "&")
12
+
13
+ if @options.has_key?(:reject)
14
+ rejactable = validate_rejectability_of( select_params(:reject, get_params.update(post_params)) )
15
+
16
+ elsif @options.has_key?(:sanitize)
17
+ get_params = sanitize_contents_of(get_params)
18
+ post_params = sanitize_contents_of(post_params)
19
+
20
+ env['QUERY_STRING'] = Rack::Utils.build_query(get_params)
21
+ env['rack.input'] = StringIO.new(Rack::Utils.build_query(post_params))
22
+ end
23
+
24
+ rejactable ? reject : continue(env)
25
+ end
26
+
27
+ private
28
+ def continue(env)
29
+ @app.call(env)
30
+ end
31
+
32
+ def reject
33
+ length, content = 0, ''
34
+ if @options[:reject].is_a?(Hash)
35
+ if (message = @options[:reject][:message]).present?
36
+ content = message
37
+ length = message.size
38
+ elsif (path = @options[:reject][:path]).present?
39
+ if (path = ::File.expand_path(path)) && ::File.exists?(path)
40
+ content = ::File.read(path)
41
+ length = content.size
42
+ end
43
+ end
44
+ end
45
+
46
+ [422, {'Content-Type' => 'text/html', 'Content-Length' => length.to_s}, [content]]
47
+ end
48
+
49
+ def validate_rejectability_of(params = {})
50
+ should_reject_request = false
51
+ params.each_pair do |param, value|
52
+ if value.is_a?(Hash)
53
+ validates_rejectability_of(value)
54
+ elsif value.is_a?(String)
55
+ next unless value.size >= 3
56
+ if ::Obscenity.profane?(value)
57
+ should_reject_request = true
58
+ break
59
+ end
60
+ else
61
+ next
62
+ end
63
+ end
64
+ should_reject_request
65
+ end
66
+
67
+ def sanitize_contents_of(params)
68
+ sanitized_params = {}
69
+ replacement_method = @options[:sanitize].is_a?(Hash) && @options[:sanitize][:replacement]
70
+ select_params(:sanitize, params).each{|param, value|
71
+ if value.is_a?(String)
72
+ next unless value.size >= 3
73
+ sanitized_params[param] = ::Obscenity.replacement(replacement_method).sanitize(value)
74
+ else
75
+ next
76
+ end
77
+ }
78
+ params.update(sanitized_params)
79
+ end
80
+
81
+ def select_params(key, params = {})
82
+ if @options[key].is_a?(Hash) && @options[key][:params].is_a?(Array)
83
+ params.select{ |param, vvalue| @options[key][:params].include?(param.to_sym) }
84
+ else
85
+ params
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,7 @@
1
+ if defined?(RSpec::Matchers)
2
+ RSpec::Matchers.define :be_profane do |expected|
3
+ match do |actual|
4
+ Obscenity.profane?(actual) == expected
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Obscenity
2
+
3
+ VERSION = '1.0.2'
4
+
5
+ end
data/lib/obscenity.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'obscenity/error'
2
+ require 'obscenity/config'
3
+ require 'obscenity/base'
4
+ require 'obscenity/version'
5
+
6
+ if defined?(::RSpec)
7
+ require 'obscenity/rspec_matcher'
8
+ end
9
+
10
+ module Obscenity extend self
11
+
12
+ attr_accessor :config
13
+
14
+ def configure(&block)
15
+ @config = Config.new(&block)
16
+ end
17
+
18
+ def config
19
+ @config ||= Config.new
20
+ end
21
+
22
+ def profane?(word)
23
+ Obscenity::Base.profane?(word)
24
+ end
25
+
26
+ def sanitize(text)
27
+ Obscenity::Base.sanitize(text)
28
+ end
29
+
30
+ def replacement(chars)
31
+ Obscenity::Base.replacement(chars)
32
+ end
33
+
34
+ def offensive(text)
35
+ Obscenity::Base.offensive(text)
36
+ end
37
+
38
+
39
+ end
40
+
data/test/helper.rb ADDED
@@ -0,0 +1,29 @@
1
+ #helper.rb
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'rspec'
5
+
6
+ # Load ActiveModel and Obscenity
7
+ require 'active_model'
8
+ require 'obscenity'
9
+ require 'obscenity/active_model'
10
+
11
+ # Dummy Model for Testing
12
+ FactoryBot.define do
13
+ factory :base_model, class: Dummy::BaseModel do
14
+ title { "Sample title" }
15
+ end
16
+ end
17
+
18
+ # Configure RSpec
19
+ RSpec.configure do |config|
20
+ config.expect_with :rspec do |expectations|
21
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
22
+ end
23
+
24
+ config.mock_with :rspec do |mocks|
25
+ mocks.verify_partial_doubles = true
26
+ end
27
+
28
+ config.shared_context_metadata_behavior = :apply_to_host_groups
29
+ end
@@ -0,0 +1 @@
1
+ We don't accept profanity
@@ -0,0 +1,70 @@
1
+ #test_active_model.rb
2
+ require 'helper'
3
+
4
+ class TestActiveModel < Test::Unit::TestCase
5
+
6
+ def generate_new_class(name, options = {})
7
+ Dummy.send(:remove_const, name) if Dummy.const_defined?(name)
8
+ klass = Class.new(Dummy::BaseModel) do
9
+ validates :title, options
10
+ end
11
+ Dummy.const_set(name, klass)
12
+ end
13
+
14
+ should "be invalid when title is profane" do
15
+ klass = generate_new_class("Post", obscenity: true)
16
+ post = klass.new(title: "He who poops, shits itself")
17
+ assert !post.valid?
18
+ assert post.errors.has_key?(:title)
19
+ assert_equal ['cannot be profane'], post.errors[:title]
20
+ end
21
+
22
+ should "be invalid when title is profane and should include a custom error message" do
23
+ klass = generate_new_class("Post", obscenity: { message: "can't be profane!" })
24
+ post = klass.new(title: "He who poops, shits itself")
25
+ assert !post.valid?
26
+ assert post.errors.has_key?(:title)
27
+ assert_equal ["can't be profane!"], post.errors[:title]
28
+ end
29
+
30
+ should "sanitize the title using the default replacement" do
31
+ klass = generate_new_class("Post", obscenity: { sanitize: true })
32
+ post = klass.new(title: "He who poops, shits itself")
33
+ assert post.valid?
34
+ assert !post.errors.has_key?(:title)
35
+ assert_equal "He who poops, $@!#% itself", post.title
36
+ end
37
+
38
+ should "sanitize the title using the :garbled replacement" do
39
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :garbled })
40
+ post = klass.new(title: "He who poops, shits itself")
41
+ assert post.valid?
42
+ assert !post.errors.has_key?(:title)
43
+ assert_equal "He who poops, $@!#% itself", post.title
44
+ end
45
+
46
+ should "sanitize the title using the :stars replacement" do
47
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :stars })
48
+ post = klass.new(title: "He who poops, shits itself")
49
+ assert post.valid?
50
+ assert !post.errors.has_key?(:title)
51
+ assert_equal "He who poops, ***** itself", post.title
52
+ end
53
+
54
+ should "sanitize the title using the :vowels replacement" do
55
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :vowels })
56
+ post = klass.new(title: "He who poops, shits itself")
57
+ assert post.valid?
58
+ assert !post.errors.has_key?(:title)
59
+ assert_equal "He who poops, sh*ts itself", post.title
60
+ end
61
+
62
+ should "sanitize the title using a custom replacement" do
63
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: '[censored]' })
64
+ post = klass.new(title: "He who poops, shits itself")
65
+ assert post.valid?
66
+ assert !post.errors.has_key?(:title)
67
+ assert_equal "He who poops, [censored] itself", post.title
68
+ end
69
+
70
+ end
data/test/test_base.rb ADDED
@@ -0,0 +1,29 @@
1
+ # test_base.rb
2
+ require 'test/unit'
3
+ require 'obscenity'
4
+
5
+ class TestObscenityBase < Test::Unit::TestCase
6
+ def setup
7
+ Obscenity.configure do |config|
8
+ config.blacklist = ["badword"]
9
+ config.whitelist = []
10
+ config.replacement = :stars
11
+ end
12
+ end
13
+
14
+ def test_methods_respond
15
+ [:blacklist, :whitelist, :profane?, :sanitize, :replacement, :offensive, :replace].each do |method|
16
+ assert_respond_to Obscenity::Base, method
17
+ end
18
+ end
19
+
20
+ def test_profane_detection
21
+ assert Obscenity::Base.profane?("badword")
22
+ assert !Obscenity::Base.profane?("cleanword")
23
+ end
24
+
25
+ def test_sanitization
26
+ assert_equal "*******", Obscenity::Base.sanitize("badword")
27
+ assert_equal "cleanword", Obscenity::Base.sanitize("cleanword")
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ #test_config.rb
2
+ require 'helper'
3
+
4
+ class TestConfig < Test::Unit::TestCase
5
+
6
+ context "#respond_to?" do
7
+ should "respond to methods and attributes" do
8
+ Obscenity::Config.new do |config|
9
+ [:blacklist, :whitelist, :replacement].each do |field|
10
+ assert config.respond_to?(field)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ should "properly set the config parameters" do
17
+ blacklist = ['ass', 'shit', 'penis']
18
+ whitelist = ['penis']
19
+ replacement = :stars
20
+
21
+ config = Obscenity::Config.new do |config|
22
+ config.blacklist = blacklist
23
+ config.whitelist = whitelist
24
+ config.replacement = replacement
25
+ end
26
+
27
+ assert_equal blacklist, config.blacklist
28
+ assert_equal whitelist, config.whitelist
29
+ assert_equal replacement, config.replacement
30
+ end
31
+
32
+ should "return default values if none is set" do
33
+ config = Obscenity::Config.new
34
+ assert_equal [], config.whitelist
35
+ assert_equal :garbled, config.replacement
36
+ assert_match /config\/blacklist.yml/, config.blacklist
37
+ end
38
+
39
+ should "return default values when default values are set" do
40
+ config = Obscenity::Config.new do |config|
41
+ config.blacklist = :default
42
+ config.replacement = :default
43
+ end
44
+ assert_equal [], config.whitelist
45
+ assert_equal :default, config.replacement
46
+ assert_match /config\/blacklist.yml/, config.blacklist
47
+ end
48
+
49
+ should "properly validate the config options" do
50
+ [:blacklist, :whitelist].each do |field|
51
+ exceptions = [
52
+ [Obscenity::UnkownContent, {}],
53
+ [Obscenity::UnkownContent, ":unkown"],
54
+ [Obscenity::EmptyContentList, []],
55
+ [Obscenity::UnkownContentFile, "'path/to/file'"],
56
+ [Obscenity::UnkownContentFile, Pathname.new("'path/to/file'")]
57
+ ].each do |klass, value|
58
+ assert_raise(klass){
59
+ Obscenity::Config.new do |config|
60
+ config.instance_eval "config.#{field} = #{value}"
61
+ end
62
+ }
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,70 @@
1
+ #test_obscenity.rb
2
+ require 'helper'
3
+
4
+ class TestObscenity < Test::Unit::TestCase
5
+
6
+ context "#respond_to?" do
7
+ should "respond to methods and attributes" do
8
+ [:configure, :config, :profane?, :sanitize, :offensive, :replacement].each do |field|
9
+ assert Obscenity.respond_to?(field)
10
+ end
11
+ end
12
+ end
13
+
14
+ # More comprehensive test in test_config.rb
15
+ context "#configure" do
16
+ should "accept a configuration block " do
17
+ assert_nothing_raised{
18
+ Obscenity.configure do |config|
19
+ config.blacklist = :default
20
+ config.replacement = :garbled
21
+ end
22
+ }
23
+ end
24
+ end
25
+
26
+ # More comprehensive test in test_config.rb
27
+ context "#config" do
28
+ should "return the current config object" do
29
+ assert_not_nil Obscenity.config
30
+ end
31
+ end
32
+
33
+ # More comprehensive test in test_base.rb
34
+ context "#profane?" do
35
+ should "validate the profanity of the given content" do
36
+ assert Obscenity.profane?('Yo, check that ass out')
37
+ assert !Obscenity.profane?('Hello world')
38
+ end
39
+ end
40
+
41
+ # More comprehensive test in test_base.rb
42
+ context "#sanitize" do
43
+ should "sanitize the given content" do
44
+ assert_equal "Yo, check that $@!#% out", Obscenity.sanitize('Yo, check that ass out')
45
+ assert_equal "Hello world", Obscenity.sanitize('Hello world')
46
+ end
47
+ end
48
+
49
+ # More comprehensive test in test_base.rb
50
+ context "#offensive" do
51
+ should "return the offensive words for the given content" do
52
+ assert_equal ['ass', 'biatch'], Obscenity.offensive('Yo, check that ass biatch')
53
+ assert_equal [], Obscenity.offensive('Hello world')
54
+ end
55
+ end
56
+
57
+ # More comprehensive test in test_base.rb
58
+ context "#replacement" do
59
+ should "sanitize the given content based on the given replacement" do
60
+ assert_equal "Yo, check that $@!#% out", Obscenity.replacement(:garbled).sanitize('Yo, check that ass out')
61
+ assert_equal "Yo, check that $@!#% out", Obscenity.replacement(:default).sanitize('Yo, check that ass out')
62
+ assert_equal "Yo, check that *ss out", Obscenity.replacement(:vowels).sanitize('Yo, check that ass out')
63
+ assert_equal "Yo, check that *h*t out", Obscenity.replacement(:nonconsonants).sanitize('Yo, check that 5hit out')
64
+ assert_equal "Yo, check that *** out", Obscenity.replacement(:stars).sanitize('Yo, check that ass out')
65
+ assert_equal "Yo, check that [censored] out", Obscenity.replacement("[censored]").sanitize('Yo, check that ass out')
66
+ assert_equal "Hello world", Obscenity.sanitize('Hello world')
67
+ end
68
+ end
69
+
70
+ end