diff --git a/lib/action_view/template_handlers/safe_haml.rb b/lib/action_view/template_handlers/safe_haml.rb index c9d6314..b80b188 100644 --- a/lib/action_view/template_handlers/safe_haml.rb +++ b/lib/action_view/template_handlers/safe_haml.rb @@ -6,7 +6,7 @@ module TemplateHandlers class SafeHaml < TemplateHandler include Compilable rescue nil # does not exist prior Rails 2.1 extend SafemodeHandler - + def self.line_offset 3 end diff --git a/lib/action_view/template_handlers/safemode_handler.rb b/lib/action_view/template_handlers/safemode_handler.rb index 7617421..44deb2b 100644 --- a/lib/action_view/template_handlers/safemode_handler.rb +++ b/lib/action_view/template_handlers/safemode_handler.rb @@ -5,23 +5,23 @@ module SafemodeHandler def valid_assigns(assigns) assigns = assigns.reject{|key, value| skip_assigns.include?(key) } end - + def delegate_methods(view) - [ :render, :params, :flash ] + - helper_methods(view) + + [ :render, :params, :flash ] + + helper_methods(view) + ActionController::Routing::Routes.named_routes.helpers end def helper_methods(view) view.class.included_modules.collect {|m| m.instance_methods(false) }.flatten.map(&:to_sym) end - + def skip_assigns [ "_cookies", "_flash", "_headers", "_params", "_request", "_response", "_session", "before_filter_chain_aborted", "ignore_missing_templates", "logger", "request_origin", "template", "template_class", "url", "variables_added", - "view_paths" ] + "view_paths" ] end end end diff --git a/lib/haml/safemode.rb b/lib/haml/safemode.rb index b6fc799..1223635 100644 --- a/lib/haml/safemode.rb +++ b/lib/haml/safemode.rb @@ -1,6 +1,6 @@ require 'haml' -module Haml +module Haml class Buffer class Jail < Safemode::Jail allow :push_script, :push_text, :_hamlout, :open_tag @@ -8,33 +8,33 @@ class Jail < Safemode::Jail end end -module Haml +module Haml class Engine - def precompile_for_safemode(filename, ignore_assigns = [], delegate_methods = []) + def precompile_for_safemode(filename, ignore_assigns = [], delegate_methods = []) @precompiled.gsub!('\\','\\\\\\') # backslashes would disappear in compile_template/modul_eval, so we escape them - - <<-CODE + + <<-CODE buffer = Haml::Buffer.new(#{options_for_buffer.inspect}) local_assigns = local_assigns.merge :_hamlout => buffer - + handler = ActionView::TemplateHandlers::SafeHaml assigns = handler.valid_assigns(@template.assigns) methods = handler.delegate_methods(self) code = %Q(#{code}); - + box = Safemode::Box.new(self, methods, #{filename.inspect}, 0) - box.eval(code, assigns, local_assigns, &lambda{ yield }) - buffer.buffer + box.eval(code, assigns, local_assigns, &lambda{ yield }) + buffer.buffer CODE # preamble = "buffer = Haml::Buffer.new(#{options_for_buffer.inspect}) # local_assigns = local_assigns.merge :_hamlout => buffer # assigns = @template.assigns.reject{|key, value| #{ignore_assigns.inspect}.include?(key) };".gsub("\n", ';') - # + # # postamble = "box = Safemode::Box.new(self, #{delegate_methods.inspect}) # box.eval(code, assigns, local_assigns, &lambda{ yield }) # buffer.buffer".gsub("\n", ';') - # + # # preamble + "code = %Q(#{@precompiled});" + postamble end end diff --git a/lib/ruby_parser_string_io_patch.diff b/lib/ruby_parser_string_io_patch.diff index 6742168..a812647 100644 --- a/lib/ruby_parser_string_io_patch.diff +++ b/lib/ruby_parser_string_io_patch.diff @@ -2,17 +2,17 @@ +++ lib/ruby_lexer.rb 2008-04-27 01:07:03.000000000 +0200 @@ -45,7 +45,7 @@ raise "bad val: #{str.inspect}" unless String === str - + self.file = file - self.lexer.src = StringIO.new(str) + self.lexer.src = RubyParser::StringIO.new(str) - + @yydebug = ENV.has_key? 'DEBUG' - + @@ -2604,104 +2604,106 @@ end end - + -class StringIO # HACK: everything in here is a hack - attr_accessor :begin_of_line, :was_begin_of_line - alias :begin_of_line? :begin_of_line @@ -22,10 +22,10 @@ + attr_accessor :begin_of_line, :was_begin_of_line + alias :begin_of_line? :begin_of_line + alias :read_all :read - + - alias :old_initialize :initialize + alias :old_initialize :initialize - + - def initialize(*args) - self.begin_of_line = true - self.was_begin_of_line = false @@ -38,26 +38,26 @@ + old_initialize(*args) + @original_string = self.string.dup + end - + - def rest - self.string[self.pos..-1] - end + def rest + self.string[self.pos..-1] + end - + - def current_line # HAHA fuck you - @original_string[0..self.pos][/\A.*__LINE__/m].split(/\n/).size - end + def current_line # HAHA fuck you + @original_string[0..self.pos][/\A.*__LINE__/m].split(/\n/).size + end - + - def read - c = self.getc + def read + c = self.getc - + - if c == ?\r then - d = self.getc - self.ungetc d if d and d != ?\n @@ -68,7 +68,7 @@ + self.ungetc d if d and d != ?\n + c = ?\n + end - + - self.was_begin_of_line = self.begin_of_line - self.begin_of_line = c == ?\n - if c and c != 0 then @@ -84,12 +84,12 @@ + end end - end - + - def match_string term, indent=false # TODO: add case insensitivity, or just remove - buffer = [] + def match_string term, indent=false # TODO: add case insensitivity, or just remove + buffer = [] - + - if indent - while c = self.read do - if c !~ /\s/ or c == "\n" or c == "\r" then @@ -106,7 +106,7 @@ - buffer << c end - end - + - term.each_byte do |c2| - c = self.read - c = self.read if c and c == "\r" @@ -126,7 +126,7 @@ + + return true end - + - return true - end + def read_line @@ -134,7 +134,7 @@ + self.was_begin_of_line = false + gets.sub(/\r\n?$/, "\n") # HACK + end - + - def read_line - self.begin_of_line = true - self.was_begin_of_line = false @@ -162,26 +162,26 @@ + end end - end - + - def unread(c) - return if c.nil? # UGH + def unread(c) + return if c.nil? # UGH - + - # HACK: only depth is 2... who cares? really I want to remove all of this - self.begin_of_line = self.was_begin_of_line || true - self.was_begin_of_line = nil + # HACK: only depth is 2... who cares? really I want to remove all of this + self.begin_of_line = self.was_begin_of_line || true + self.was_begin_of_line = nil - + - c = c[0] if String === c - self.ungetc c - end + c = c[0] if String === c + self.ungetc c + end - + - def unread_many str - str.split(//).reverse.each do |c| - unread c diff --git a/lib/safemode.rb b/lib/safemode.rb index bbefa17..70bf8a6 100644 --- a/lib/safemode.rb +++ b/lib/safemode.rb @@ -26,7 +26,7 @@ class << self def jail(obj) find_jail_class(obj.is_a?(Class) ? obj : obj.class).new obj end - + def find_jail_class(klass) while klass != Object return klass.const_get('Jail') if klass.const_defined?('Jail') @@ -35,24 +35,24 @@ def find_jail_class(klass) Jail end end - + define_core_jail_classes - + class Box def initialize(delegate = nil, delegate_methods = [], filename = nil, line = nil) @scope = Scope.new(delegate, delegate_methods) @filename = filename @line = line - end + end def eval(code, assigns = {}, locals = {}, &block) code = Parser.jail(code) binding = @scope.bind(assigns, locals, &block) result = Kernel.eval(code, binding, @filename || __FILE__, @line || __LINE__) end - + def output @scope.output - end + end end end diff --git a/lib/safemode/core_ext.rb b/lib/safemode/core_ext.rb index 9d89be5..30fb98a 100644 --- a/lib/safemode/core_ext.rb +++ b/lib/safemode/core_ext.rb @@ -1,12 +1,12 @@ -module Kernel +module Kernel def silently(&blk) old_verbose, $VERBOSE = $VERBOSE, nil yield $VERBOSE = old_verbose - end + end end -class Module +class Module def undef_methods(*methods) methods.each { |name| undef_method(name) } end @@ -29,7 +29,7 @@ def to_jail # Safemode.jail collect{ |obj| obj.to_jail } # end # end -# +# # class Hash # def to_jail # hash = {} diff --git a/lib/safemode/exceptions.rb b/lib/safemode/exceptions.rb index dffadd3..642837a 100644 --- a/lib/safemode/exceptions.rb +++ b/lib/safemode/exceptions.rb @@ -1,19 +1,19 @@ module Safemode class Error < RuntimeError; end - + class SecurityError < Error @@types = { :const => 'constant', :xstr => 'shell command', :fcall => 'method', :vcall => 'method', :gvar => 'global variable' } - + def initialize(type, value = nil) type = @@types[type] if @@types.include?(type) super "Safemode doesn't allow to access '#{type}'" + (value ? " on #{value}" : '') end end - + class NoMethodError < Error def initialize(method, jail, source = nil) super "undefined method '#{method}' for #{jail}" + (source ? " (#{source})" : '') diff --git a/lib/safemode/scope.rb b/lib/safemode/scope.rb index 1c3ab6d..189b487 100644 --- a/lib/safemode/scope.rb +++ b/lib/safemode/scope.rb @@ -1,11 +1,11 @@ module Safemode class Scope < Blankslate def initialize(delegate = nil, delegate_methods = []) - @delegate = delegate + @delegate = delegate @delegate_methods = delegate_methods @locals = {} end - + def bind(instance_vars = {}, locals = {}, &block) @locals = symbolize_keys(locals) # why can't I just pull them to local scope in the same way like instance_vars? instance_vars = symbolize_keys(instance_vars) @@ -13,19 +13,19 @@ def bind(instance_vars = {}, locals = {}, &block) @_safemode_output = '' binding end - + def to_jail self end - + def puts(*args) print args.to_s + "\n" end - def print(*args) + def print(*args) @_safemode_output += args.to_s end - + def output @_safemode_output end @@ -39,18 +39,18 @@ def method_missing(method, *args, &block) raise Safemode::SecurityError.new(method, "#") end end - + private - + def symbolize_keys(hash) - hash.inject({}) do |hash, (key, value)| + hash.inject({}) do |hash, (key, value)| hash[key.to_s.intern] = value hash end end - + def unjail_args(args) - args.collect do |arg| + args.collect do |arg| arg.class.name =~ /::Jail$/ ? arg.instance_variable_get(:@source) : arg end end diff --git a/test/test_erb_eval.rb b/test/test_erb_eval.rb index 1ac52a2..171dcd5 100644 --- a/test/test_erb_eval.rb +++ b/test/test_erb_eval.rb @@ -2,7 +2,7 @@ class TestERBEval < Test::Unit::TestCase include TestHelper - + def setup @box = Safemode::Box.new @locals = { :article => Article.new } @@ -18,45 +18,45 @@ def test_some_stuff_that_should_work assert_nothing_raised{ @box.eval code } end end - + def test_should_turn_assigns_to_jails assert_raise_no_method "@article.system", @assigns, &@erb_parse end - + def test_should_turn_locals_to_jails code = @erb_parse.call "article.system" assert_raise(Safemode::NoMethodError){ @box.eval code, {}, @locals } end - + def test_should_allow_method_access_on_assigns code = @erb_parse.call "@article.title" assert_nothing_raised{ @box.eval code, @assigns } end - + def test_should_allow_method_access_on_locals code = @erb_parse.call "article.title" assert_nothing_raised{ @box.eval code, {}, @locals } end - + def test_should_not_raise_on_if_using_return_values code = @erb_parse.call "if @article.is_article?\n 1\n end" assert_nothing_raised{ @box.eval code, @assigns } end - + def test_should_work_with_if_using_return_values code = @erb_parse.call "if @article.is_article? then 1 end" assert_equal @box.eval(code, @assigns), "1" # ERB calls to_s on the result of the if block end - + def test__FILE__should_not_render_filename code = @erb_parse.call "__FILE__" assert_equal '(string)', @box.eval(code) end - + def test_interpolated_xstr_should_raise_security assert_raise_security '"#{`ls -a`}"' - end - + end + TestHelper.no_method_error_raising_calls.each do |call| call.gsub!('"', '\\\\"') class_eval %Q( @@ -65,7 +65,7 @@ def test_calling_#{call.gsub(/[\W]/, '_')}_should_raise_no_method end ) end - + TestHelper.security_error_raising_calls.each do |call| call.gsub!('"', '\\\\"') class_eval %Q( @@ -73,6 +73,6 @@ def test_calling_#{call.gsub(/[\W]/, '_')}_should_raise_security assert_raise_security "#{call}", @assigns, @locals end ) - end + end end diff --git a/test/test_safemode_parser.rb b/test/test_safemode_parser.rb index ff95d6f..d827ba7 100644 --- a/test/test_safemode_parser.rb +++ b/test/test_safemode_parser.rb @@ -4,23 +4,23 @@ class TestSafemodeParser < Test::Unit::TestCase def test_vcall_should_be_jailed assert_jailed 'to_jail.a.to_jail.class', 'a.class' end - + def test_call_should_be_jailed assert_jailed '(1.to_jail + 1).to_jail.class', '(1 + 1).class' end - + def test_estr_should_be_jailed assert_jailed '"#{1.to_jail.class}"', '"#{1.class}"' end - + def test_if_should_be_usable_for_erb assert_jailed "if true then\n 1\nend", "if true\n 1\n end" end - + def test_if_else_should_be_usable_for_erb assert_jailed "if true then\n 1\n else\n2\nend", "if true\n 1\n else\n2\n end" end - + def test_ternary_should_be_usable_for_erb assert_jailed "if true then\n 1\n else\n2\nend", "true ? 1 : 2" end @@ -38,11 +38,11 @@ def test_block_pass_is_disabled end private - + def assert_jailed(expected, code) assert_equal expected.gsub(' ', ''), jail(code).gsub(' ', '') - end - + end + def jail(code) Safemode::Parser.jail(code) end