obscenity 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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