obscenity 1.0.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.
@@ -0,0 +1,36 @@
1
+ require 'obscenity/error'
2
+ require 'obscenity/config'
3
+ require 'obscenity/base'
4
+ require 'obscenity/version'
5
+
6
+ module Obscenity extend self
7
+
8
+ attr_accessor :config
9
+
10
+ def configure(&block)
11
+ @config = Config.new(&block)
12
+ end
13
+
14
+ def config
15
+ @config ||= Config.new
16
+ end
17
+
18
+ def profane?(word)
19
+ Obscenity::Base.profane?(word)
20
+ end
21
+
22
+ def sanitize(text)
23
+ Obscenity::Base.sanitize(text)
24
+ end
25
+
26
+ def replacement(chars)
27
+ Obscenity::Base.replacement(chars)
28
+ end
29
+
30
+ def offensive(text)
31
+ Obscenity::Base.offensive(text)
32
+ end
33
+
34
+
35
+ end
36
+
@@ -0,0 +1,18 @@
1
+ if defined?(ActiveModel)
2
+ module ActiveModel
3
+ module Validations
4
+ class ObscenityValidator < ActiveModel::EachValidator
5
+
6
+ def validate_each(record, attribute, value)
7
+ if options.present? && options.has_key?(:sanitize)
8
+ object = record.respond_to?(:[]) ? record[attribute] : record.send(attribute)
9
+ object = Obscenity.replacement(options[:replacement]).sanitize(object)
10
+ else
11
+ record.errors.add(attribute, options[:message] || 'cannot be profane') if Obscenity.profane?(value)
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ module Obscenity
2
+ class Base
3
+ class << self
4
+
5
+ def blacklist
6
+ @blacklist ||= set_list_content(Obscenity.config.blacklist)
7
+ end
8
+
9
+ def blacklist=(value)
10
+ @blacklist = value == :default ? set_list_content(Obscenity::Config.new.blacklist) : value
11
+ end
12
+
13
+ def whitelist
14
+ @whitelist ||= set_list_content(Obscenity.config.whitelist)
15
+ end
16
+
17
+ def whitelist=(value)
18
+ @whitelist = value == :default ? set_list_content(Obscenity::Config.new.whitelist) : value
19
+ end
20
+
21
+ def profane?(text)
22
+ return(false) unless text.to_s.size >= 3
23
+ blacklist.each do |foul|
24
+ return(true) if text =~ /\b#{foul}\b/i && !whitelist.include?(foul)
25
+ end
26
+ false
27
+ end
28
+
29
+ def sanitize(text)
30
+ return(text) unless text.to_s.size >= 3
31
+ blacklist.each do |foul|
32
+ text.gsub!(/\b#{foul}\b/i, replace(foul)) unless whitelist.include?(foul)
33
+ end
34
+ @scoped_replacement = nil
35
+ text
36
+ end
37
+
38
+ def replacement(chars)
39
+ @scoped_replacement = chars
40
+ self
41
+ end
42
+
43
+ def offensive(text)
44
+ words = []
45
+ return(words) unless text.to_s.size >= 3
46
+ blacklist.each do |foul|
47
+ words << foul if text =~ /\b#{foul}\b/i && !whitelist.include?(foul)
48
+ end
49
+ words.uniq
50
+ end
51
+
52
+ def replace(word)
53
+ content = @scoped_replacement || Obscenity.config.replacement
54
+ case content
55
+ when :vowels then word.gsub(/[aeiou]/i, '*')
56
+ when :stars then '*' * word.size
57
+ when :default, :garbled then '$@!#%'
58
+ else content
59
+ end
60
+ end
61
+
62
+ private
63
+ def set_list_content(list)
64
+ case list
65
+ when Array then list
66
+ when String, Pathname then YAML.load_file( list.to_s )
67
+ else []
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ 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.exists?(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,5 @@
1
+ module Obscenity
2
+
3
+ VERSION = '1.0.0'
4
+
5
+ end
@@ -0,0 +1,84 @@
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 = "obscenity"
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Thiago Jackiw"]
12
+ s.date = "2012-05-28"
13
+ s.description = " Obscenity is a profanity filter gem for Ruby/Rubinius, Rails (through ActiveModel), and Rack middleware "
14
+ s.email = "tjackiw@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ ".travis.yml",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "config/blacklist.yml",
29
+ "config/international.yml",
30
+ "lib/obscenity.rb",
31
+ "lib/obscenity/active_model.rb",
32
+ "lib/obscenity/base.rb",
33
+ "lib/obscenity/config.rb",
34
+ "lib/obscenity/error.rb",
35
+ "lib/obscenity/rack.rb",
36
+ "lib/obscenity/version.rb",
37
+ "obscenity.gemspec",
38
+ "test/helper.rb",
39
+ "test/static/422.html",
40
+ "test/test_active_model.rb",
41
+ "test/test_base.rb",
42
+ "test/test_config.rb",
43
+ "test/test_obscenity.rb",
44
+ "test/test_rack.rb",
45
+ "test/test_version.rb"
46
+ ]
47
+ s.homepage = "http://github.com/tjackiw/obscenity"
48
+ s.licenses = ["MIT"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.24"
51
+ s.summary = "Obscenity is a profanity filter gem for Ruby/Rubinius, Rails (through ActiveModel), and Rack middleware"
52
+ s.test_files = ["test/helper.rb", "test/static/422.html", "test/test_active_model.rb", "test/test_base.rb", "test/test_config.rb", "test/test_obscenity.rb", "test/test_rack.rb", "test/test_version.rb"]
53
+
54
+ if s.respond_to? :specification_version then
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
59
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
60
+ s.add_development_dependency(%q<bundler>, [">= 0"])
61
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
62
+ s.add_development_dependency(%q<activemodel>, ["~> 3.0"])
63
+ s.add_development_dependency(%q<rack>, [">= 0"])
64
+ s.add_development_dependency(%q<rake>, [">= 0"])
65
+ else
66
+ s.add_dependency(%q<shoulda>, [">= 0"])
67
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
68
+ s.add_dependency(%q<bundler>, [">= 0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
70
+ s.add_dependency(%q<activemodel>, ["~> 3.0"])
71
+ s.add_dependency(%q<rack>, [">= 0"])
72
+ s.add_dependency(%q<rake>, [">= 0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<shoulda>, [">= 0"])
76
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
77
+ s.add_dependency(%q<bundler>, [">= 0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
79
+ s.add_dependency(%q<activemodel>, ["~> 3.0"])
80
+ s.add_dependency(%q<rack>, [">= 0"])
81
+ s.add_dependency(%q<rake>, [">= 0"])
82
+ end
83
+ end
84
+
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+
16
+ require 'active_model'
17
+ require 'obscenity'
18
+ require 'obscenity/active_model'
19
+
20
+ module Dummy
21
+ class BaseModel
22
+ include ActiveModel::Validations
23
+
24
+ attr_accessor :title
25
+
26
+ def initialize(attr_names)
27
+ attr_names.each{ |k,v| send("#{k}=", v) }
28
+ end
29
+ end
30
+ end
31
+
32
+ class Test::Unit::TestCase
33
+ end
@@ -0,0 +1 @@
1
+ We don't accept profanity
@@ -0,0 +1,69 @@
1
+ require 'helper'
2
+
3
+ class TestActiveModel < Test::Unit::TestCase
4
+
5
+ def generate_new_class(name, options = {})
6
+ Dummy.send(:remove_const, name) if Dummy.const_defined?(name)
7
+ klass = Class.new(Dummy::BaseModel) do
8
+ validates :title, options
9
+ end
10
+ Dummy.const_set(name, klass)
11
+ end
12
+
13
+ should "be invalid when title is profane" do
14
+ klass = generate_new_class("Post", obscenity: true)
15
+ post = klass.new(title: "He who poops, shits itself")
16
+ assert !post.valid?
17
+ assert post.errors.has_key?(:title)
18
+ assert_equal ['cannot be profane'], post.errors[:title]
19
+ end
20
+
21
+ should "be invalid when title is profane and should include a custom error message" do
22
+ klass = generate_new_class("Post", obscenity: { message: "can't be profane!" })
23
+ post = klass.new(title: "He who poops, shits itself")
24
+ assert !post.valid?
25
+ assert post.errors.has_key?(:title)
26
+ assert_equal ["can't be profane!"], post.errors[:title]
27
+ end
28
+
29
+ should "sanitize the title using the default replacement" do
30
+ klass = generate_new_class("Post", obscenity: { sanitize: true })
31
+ post = klass.new(title: "He who poops, shits itself")
32
+ assert post.valid?
33
+ assert !post.errors.has_key?(:title)
34
+ assert_equal "He who poops, $@!#% itself", post.title
35
+ end
36
+
37
+ should "sanitize the title using the :garbled replacement" do
38
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :garbled })
39
+ post = klass.new(title: "He who poops, shits itself")
40
+ assert post.valid?
41
+ assert !post.errors.has_key?(:title)
42
+ assert_equal "He who poops, $@!#% itself", post.title
43
+ end
44
+
45
+ should "sanitize the title using the :stars replacement" do
46
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :stars })
47
+ post = klass.new(title: "He who poops, shits itself")
48
+ assert post.valid?
49
+ assert !post.errors.has_key?(:title)
50
+ assert_equal "He who poops, ***** itself", post.title
51
+ end
52
+
53
+ should "sanitize the title using the :vowels replacement" do
54
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: :vowels })
55
+ post = klass.new(title: "He who poops, shits itself")
56
+ assert post.valid?
57
+ assert !post.errors.has_key?(:title)
58
+ assert_equal "He who poops, sh*ts itself", post.title
59
+ end
60
+
61
+ should "sanitize the title using a custom replacement" do
62
+ klass = generate_new_class("Post", obscenity: { sanitize: true, replacement: '[censored]' })
63
+ post = klass.new(title: "He who poops, shits itself")
64
+ assert post.valid?
65
+ assert !post.errors.has_key?(:title)
66
+ assert_equal "He who poops, [censored] itself", post.title
67
+ end
68
+
69
+ end