Skip to content
This repository has been archived by the owner on May 1, 2022. It is now read-only.

Commit

Permalink
Change module to ActsAsSanitiled, add rails/init.rb, and tidy up requ…
Browse files Browse the repository at this point in the history
…ires
  • Loading branch information
gtd committed Oct 15, 2009
1 parent 335eb59 commit 070ad7c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 112 deletions.
4 changes: 1 addition & 3 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
= Acts as Sanitiled

This plugin, based on Chris Wanstrath's venerable acts_as_textiled, extends the
automatic textiling functionality to sanitization as well using as its basis Ryan
Grove's powerful yet simple Sanitize gem.
This plugin, based on Chris Wanstrath's venerable acts_as_textiled, extends the automatic textiling functionality to sanitization as well using as its basis Ryan Grove's powerful yet simple Sanitize gem.

The reasoning behind this approach is simple. Filtering input before it is saved to the database (as xss_terminate and many other popular plugins do) often fails to preserve user intent. On the other hand, filtering output at the template level is error prone, and you are begging to get pwned. Short of some sort of taint mode (which Rails 3 will have!), I believe the method employed by acts_as_textiled is the next best thing: you get safe output by default, but input is never corrupted.

Expand Down
196 changes: 93 additions & 103 deletions lib/acts_as_sanitiled.rb
Original file line number Diff line number Diff line change
@@ -1,132 +1,122 @@
require 'sanitize' unless defined? Sanitize
require 'rubygems'
require 'sanitize'
require 'RedCloth'

begin
require 'RedCloth' unless defined? RedCloth
rescue LoadError
nil
end

module Err
module Acts #:nodoc: all
module Textiled
def self.included(klass)
klass.extend ClassMethods
end
module ActsAsSanitiled #:nodoc: all
def self.included(klass)
klass.extend ClassMethods
end

module ClassMethods
def acts_as_textiled(*attributes)
@textiled_attributes ||= []
module ClassMethods
def acts_as_textiled(*attributes)
@textiled_attributes ||= []

@textiled_unicode = String.new.respond_to? :chars
@textiled_unicode = String.new.respond_to? :chars

options = attributes.last.is_a?(Hash) ? attributes.pop : {}
skip_textile = options.delete(:skip_textile)
skip_sanitize = options.delete(:skip_sanitize)
options = attributes.last.is_a?(Hash) ? attributes.pop : {}
skip_textile = options.delete(:skip_textile)
skip_sanitize = options.delete(:skip_sanitize)

raise 'Both textile and sanitize were skipped' if skip_textile && skip_sanitize
raise 'Both textile and sanitize were skipped' if skip_textile && skip_sanitize

sanitize_options = options.empty? ? Sanitize::Config::RELAXED : options
red_cloth_options = attributes.last && attributes.last.is_a?(Array) ? attributes.pop : []
sanitize_options = options.empty? ? Sanitize::Config::RELAXED : options
red_cloth_options = attributes.last && attributes.last.is_a?(Array) ? attributes.pop : []

raise 'No attributes were specified to filter' if attributes.empty?
raise 'No attributes were specified to filter' if attributes.empty?

type_options = %w( plain source )
type_options = %w( plain source )

attributes.each do |attribute|
define_method(attribute) do |*type|
type = type.first
attributes.each do |attribute|
define_method(attribute) do |*type|
type = type.first

if type.nil? && self[attribute]
if textiled[attribute.to_s].nil?
string = self[attribute]
string = RedCloth.new(string, red_cloth_options).to_html unless skip_textile
string = Sanitize.clean(string, sanitize_options) unless skip_sanitize
textiled[attribute.to_s] = string
end
textiled[attribute.to_s]
elsif type.nil? && self[attribute].nil?
nil
elsif type_options.include?(type.to_s)
send("#{attribute}_#{type}")
else
raise "I don't understand the `#{type}' option. Try #{type_options.join(' or ')}."
end
if type.nil? && self[attribute]
if textiled[attribute.to_s].nil?
string = self[attribute]
string = RedCloth.new(string, red_cloth_options).to_html unless skip_textile
string = Sanitize.clean(string, sanitize_options) unless skip_sanitize
textiled[attribute.to_s] = string
end

define_method("#{attribute}_plain", proc { strip_redcloth_html(__send__(attribute)) if __send__(attribute) } )
define_method("#{attribute}_source", proc { __send__("#{attribute}_before_type_cast") } )

@textiled_attributes << attribute
textiled[attribute.to_s]
elsif type.nil? && self[attribute].nil?
nil
elsif type_options.include?(type.to_s)
send("#{attribute}_#{type}")
else
raise "I don't understand the `#{type}' option. Try #{type_options.join(' or ')}."
end

include Err::Acts::Textiled::InstanceMethods
end

def textiled_attributes
Array(@textiled_attributes)
end
define_method("#{attribute}_plain", proc { strip_redcloth_html(__send__(attribute)) if __send__(attribute) } )
define_method("#{attribute}_source", proc { __send__("#{attribute}_before_type_cast") } )

@textiled_attributes << attribute
end

module InstanceMethods
def textiled
textiled? ? (@textiled ||= {}) : @attributes.dup
end
include ActsAsSanitiled::InstanceMethods
end

def textiled?
@is_textiled != false
end
def textiled_attributes
Array(@textiled_attributes)
end
end

def textiled=(bool)
@is_textiled = !!bool
end
module InstanceMethods
def textiled
textiled? ? (@textiled ||= {}) : @attributes.dup
end

def textilize
self.class.textiled_attributes.each { |attr| __send__(attr) }
end
def textiled?
@is_textiled != false
end

def reload
textiled.clear
super
end
def textiled=(bool)
@is_textiled = !!bool
end

def write_attribute(attr_name, value)
textiled[attr_name.to_s] = nil
super
end
def textilize
self.class.textiled_attributes.each { |attr| __send__(attr) }
end

private
def strip_redcloth_html(html)
returning html.dup.gsub(html_regexp, '') do |h|
redcloth_glyphs.each do |(entity, char)|
sub = [ :gsub!, entity, char ]
@textiled_unicode ? h.chars.send(*sub) : h.send(*sub)
end
end
end
def reload
textiled.clear
super
end

def redcloth_glyphs
[[ '&#8217;', "'" ],
[ '&#8216;', "'" ],
[ '&lt;', '<' ],
[ '&gt;', '>' ],
[ '&#8221;', '"' ],
[ '&#8220;', '"' ],
[ '&#8230;', '...' ],
[ '\1&#8212;', '--' ],
[ ' &rarr; ', '->' ],
[ ' &#8211; ', '-' ],
[ '&#215;', 'x' ],
[ '&#8482;', '(TM)' ],
[ '&#174;', '(R)' ],
[ '&#169;', '(C)' ]]
end
def write_attribute(attr_name, value)
textiled[attr_name.to_s] = nil
super
end

def html_regexp
%r{<(?:[^>"']+|"(?:\\.|[^\\"]+)*"|'(?:\\.|[^\\']+)*')*>}xm
private
def strip_redcloth_html(html)
returning html.dup.gsub(html_regexp, '') do |h|
redcloth_glyphs.each do |(entity, char)|
sub = [ :gsub!, entity, char ]
@textiled_unicode ? h.chars.send(*sub) : h.send(*sub)
end
end
end

def redcloth_glyphs
[[ '&#8217;', "'" ],
[ '&#8216;', "'" ],
[ '&lt;', '<' ],
[ '&gt;', '>' ],
[ '&#8221;', '"' ],
[ '&#8220;', '"' ],
[ '&#8230;', '...' ],
[ '\1&#8212;', '--' ],
[ ' &rarr; ', '->' ],
[ ' &#8211; ', '-' ],
[ '&#215;', 'x' ],
[ '&#8482;', '(TM)' ],
[ '&#174;', '(R)' ],
[ '&#169;', '(C)' ]]
end

def html_regexp
%r{<(?:[^>"']+|"(?:\\.|[^\\"]+)*"|'(?:\\.|[^\\']+)*')*>}xm
end
end
end

ActiveRecord::Base.send(:include, Err::Acts::Textiled)
1 change: 1 addition & 0 deletions rails/init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ActiveRecord::Base.send(:include, ActsAsSanitiled)
10 changes: 4 additions & 6 deletions test/helper.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
$:.unshift File.dirname(__FILE__) + '/../lib'

begin
require 'rubygems'
require 'acts_as_sanitiled'
require 'yaml'
require 'mocha'
require 'active_support'
require 'test/spec'
require 'RedCloth'
require 'active_support'
rescue LoadError
puts "acts_as_textiled requires the mocha and test-spec gems to run its tests"
puts "acts_as_sanitiled requires yaml, and test-spec to run its tests"
exit
end

Expand Down Expand Up @@ -53,7 +51,7 @@ def self.find(id)
end
end unless defined? ActiveRecord

require 'acts_as_sanitiled.rb'
ActiveRecord::Base.send(:include, ActsAsSanitiled)

class Author < ActiveRecord::Base
acts_as_textiled :blog, [:lite_mode]
Expand Down

0 comments on commit 070ad7c

Please sign in to comment.