Permalink
Browse files

extraction

  • Loading branch information...
0 parents commit 69b4098f129a6ed7407209463bba3d80f615f352 @dan-manges committed Sep 16, 2008
@@ -0,0 +1,7 @@
+require "rake/testtask"
+
+task :default => :test
+
+Rake::TestTask.new do |t|
+ t.pattern = "test/**/*_test.rb"
+end
1 TODO
@@ -0,0 +1 @@
+* Tidy?
@@ -0,0 +1,41 @@
+require "xss_killer/action_controller_extension"
+require "xss_killer/active_record_extension"
+
+ActiveRecord::Base.send :include, XssKiller::ActiveRecordExtension
+ActiveRecord::Base.extend XssKiller::ActiveRecordExtension::ClassMethods
+ActionController::Base.send :include, XssKiller::ActionControllerExtension
+
+module XssKiller
+ @records_to_escape = []
+ @rendering = false
+
+ def self.render_format
+ @render_format
+ end
+
+ def self.rendering?
+ @rendering
+ end
+
+ def self.rendering(format, template, &block)
+ @template = template
+ @render_format = format
+ @rendering = true
+ while record = @records_to_escape.shift
+ record.kill_xss(template) if format == :html
+ end
+ yield
+ ensure
+ @render_format = nil
+ @rendering = false
+ @template = nil
+ end
+
+ def self.track(active_record)
+ if rendering?
+ active_record.kill_xss(@template) if @render_format == :html
+ else
+ @records_to_escape << active_record
+ end
+ end
+end
@@ -0,0 +1,30 @@
+module XssKiller
+ module ActionControllerExtension
+ def self.included(klass)
+ klass.class_eval do
+ alias_method_chain :render, :xss_killer
+ end
+ end
+
+ def self.mime_type_for_handler(handler)
+ case handler.class.name # TODO: not very ducky
+ when "ActionView::TemplateHandlers::ERB" then Mime::HTML.to_sym
+ when "ActionView::TemplateHandlers::RJS" then Mime::JS.to_sym
+ when "ActionView::TemplateHandlers::Builder" then Mime::XML.to_sym
+ end
+ end
+
+ def render_with_xss_killer(options = nil, extra_options = {}, &block)
+ if options # explicit render
+ mime_type = response.content_type ? Mime::Type.lookup(response.content_type.to_s).to_sym : Mime::HTML.to_sym
+ else # implicit render
+ handler = ActionView::Template.new(@template, default_template_name, true).handler
+ mime_type = ActionControllerExtension.mime_type_for_handler(handler) || raise("TODO: decide what to do")
+ end
+
+ XssKiller.rendering mime_type, @template do
+ render_without_xss_killer options, extra_options, &block
+ end
+ end
+ end
+end
@@ -0,0 +1,70 @@
+module XssKiller
+ module ActiveRecordExtension
+ module ClassMethods
+ def kill_xss(options = {})
+ include XssKiller::ActiveRecordExtension
+ @xss_killer_options = options
+ end
+
+ def xss_killer_options
+ @xss_killer_options || {}
+ end
+ end
+
+ def self.included(base)
+ base.class_eval do
+ after_find :track_for_xss_killing
+ after_initialize :track_for_xss_killing
+ end
+ end
+
+ def kill_xss(template)
+ @template = template
+ extend html_escaping_module
+ end
+
+ def after_find
+ end
+
+ def after_initialize
+ end
+
+ def track_for_xss_killing
+ XssKiller.track self
+ end
+
+ protected
+
+ def html_escaping_module
+ if self.class.const_defined?("XSSKilling")
+ return self.class.const_get("XSSKilling")
+ end
+ if !self.class.generated_methods?
+ self.class.define_attribute_methods
+ end
+ mod = Module.new
+ self.class.column_names.each do |method|
+ mod.module_eval <<-END, __FILE__, __LINE__
+ def #{method}(kill_xss = true)
+ value = super()
+ if value.is_a?(String) && kill_xss
+ if self.class.xss_killer_options[:allow_injection] &&
+ self.class.xss_killer_options[:allow_injection].map(&:to_s).include?(#{method.to_s.inspect})
+ value
+ elsif self.class.xss_killer_options[:sanitize] &&
+ self.class.xss_killer_options[:sanitize].map(&:to_s).include?(#{method.to_s.inspect})
+ sanitized = @template.sanitize value
+ formatted = @template.simple_format sanitized
+ else
+ ERB::Util.html_escape value
+ end
+ else
+ value
+ end
+ end
+ END
+ end
+ self.class.const_set "XSSKilling", mod
+ end
+ end
+end
@@ -0,0 +1,45 @@
+require File.dirname(__FILE__) + "/test_helper"
+
+class ActionControllerExtensionTest < Test::Unit::TestCase
+
+ def setup
+ @controller = FoosController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ test "escapes html if format is html" do
+ @request.env["HTTP_ACCEPT"] = "text/html"
+ get :xss_using_respond_to_block, :attr_to_kill_xss => "<dan>"
+ assert_equal "&lt;dan&gt;", assigns(:foo).attr_to_kill_xss
+ end
+
+ test "does not escacpe html if format is xml" do
+ @request.env["HTTP_ACCEPT"] = "text/xml"
+ get :xss_using_respond_to_block, :attr_to_kill_xss => "<dan>"
+ assert_equal "<dan>", assigns(:foo).attr_to_kill_xss
+ end
+
+ test "escapes html if using implicit html template" do
+ get :implicit_html_render, :attr_to_kill_xss => "<dan>"
+ assert_equal "&lt;dan&gt;", assigns(:foo).attr_to_kill_xss
+ end
+
+ test "does not escape html if format is implicit xml" do
+ get :implicit_xml_render, :attr_to_kill_xss => "<dan>"
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<dan>", assigns(:foo).attr_to_kill_xss
+ end
+
+ test "records in an array get html escaped" do
+ get :foos, :attr_to_kill_xss => "<dan>"
+ assert_equal "&lt;dan&gt;", assigns(:foos)[0].attr_to_kill_xss
+ end
+
+ test "records loaded after render is called get escaped" do
+ foo = Foo.create! :other_foo => Foo.create!(:attr_to_kill_xss => "<dan>")
+ get :render_other_foos_attr_to_kill_xss, :id => foo.id
+ assert_equal "&lt;dan&gt;", @response.body
+ end
+
+end
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + "/test_helper"
+
+class ActiveRecordExtensionTest < Test::Unit::TestCase
+ test "setting which attributes are escaped" do
+ foo = Foo.new :attr_to_allow_injection => "<js>", :attr_to_kill_xss => "<js>"
+ XssKiller.rendering :html, ActionView::Base.new do
+ assert_equal "<js>", foo.attr_to_allow_injection
+ assert_equal "&lt;js&gt;", foo.attr_to_kill_xss
+ end
+ end
+
+ test "using sanitize" do
+ link = "<p><a href=\"http://www.google.com\">google</a></p>"
+ foo = Foo.new :attr_to_sanitize => "<a href='http://www.google.com'>google</a>"
+ XssKiller.rendering :html, ActionView::Base.new do
+ assert_equal link, foo.attr_to_sanitize
+ end
+
+ js = "<script></script>"
+ foo = Foo.new :attr_to_sanitize => js
+ XssKiller.rendering :html, ActionView::Base.new do
+ assert_equal "<p></p>", foo.attr_to_sanitize
+ end
+ end
+
+ test "if using sanitize it also uses simple_format" do
+ formatted = "<p><a href=\"http://www.google.com\">google\n<br />line2</a></p>"
+ foo = Foo.new :attr_to_sanitize => "<a href='http://www.google.com'>google\nline2</a>"
+ XssKiller.rendering :html, ActionView::Base.new do
+ assert_equal formatted, foo.attr_to_sanitize
+ end
+ end
+end
@@ -0,0 +1,51 @@
+class FoosController < ActionController::Base
+ def implicit_html_render
+ @foo = Foo.new :attr_to_kill_xss => params[:attr_to_kill_xss]
+ end
+
+ def implicit_xml_render
+ @foo = Foo.new :attr_to_kill_xss => params[:attr_to_kill_xss]
+ end
+
+ def inline_render_param_q
+ render :inline => "<%= params[:q] %>"
+ end
+
+ def render_other_foos_attr_to_kill_xss
+ @foo = Foo.find params[:id]
+ render :inline => "<%= @foo.other_foo.attr_to_kill_xss %>"
+ end
+
+ def new_person
+ @person = Person.new :birthday => params[:birthday]
+ render :text => ""
+ end
+
+ def not_presentable
+ @not_presentable = Object.new
+ render :text => ""
+ end
+
+ def foos
+ foo = Foo.create!(:attr_to_kill_xss => params[:attr_to_kill_xss])
+ @foos = Foo.find(:all, :conditions => {:id => foo.id})
+ render :nothing => true
+ end
+
+ def xss_using_respond_to_block
+ foo = Foo.create! :attr_to_kill_xss => params[:attr_to_kill_xss]
+ @foo = Foo.find(foo.id)
+ respond_to do |format|
+ format.html { render :inline => "<%= debug @foo %>"}
+ format.xml { render :xml => @foo.to_xml }
+ end
+ end
+end
+
+# Re-raise errors caught by the controller.
+class FoosController; def rescue_action(e) raise e end; end
+
+FoosController.instance_methods(false).each do |action|
+ ActionController::Routing::Routes.add_route \
+ "/foos/#{action}", :controller => "foos", :action => action
+end
@@ -0,0 +1,40 @@
+# rails
+require "rubygems"
+gem "rails", "2.1.0"
+require "active_record"
+require "action_controller"
+require "action_controller/test_process"
+
+# xss_killer
+$LOAD_PATH << File.dirname(__FILE__) + "/lib"
+require "xss_killer"
+
+# test/unit
+require "test/unit"
+Test::Unit::TestCase.class_eval do
+ def self.test(method, &block)
+ define_method "test_#{method}".gsub(/\W/,"_"), &block
+ end
+end
+
+# test setup
+ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
+ActiveRecord::Schema.define :version => 1 do
+ create_table "foos" do |t|
+ t.string "attr_to_kill_xss"
+ t.string "attr_to_allow_injection"
+ t.string "attr_to_sanitize"
+ t.integer "attr_integer"
+ t.datetime "attr_datetime"
+ t.integer "other_foo_id"
+ end
+end
+
+require File.dirname(__FILE__) + "/foos_controller"
+ActionController::Base.view_paths = ["#{File.dirname(__FILE__)}/views"]
+
+class Foo < ActiveRecord::Base
+ belongs_to :other_foo, :class_name => "Foo", :foreign_key => "other_foo_id"
+ kill_xss :allow_injection => ["attr_to_allow_injection"],
+ :sanitize => ["attr_to_sanitize"]
+end
@@ -0,0 +1 @@
+<div><%= @foo.attr_to_kill_xss %></div>
@@ -0,0 +1,4 @@
+xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
+
+xml.foo do
+end

0 comments on commit 69b4098

Please sign in to comment.