safemode 1.3.8 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b248c163601057c5120218eed0df015276c212b4e1fba1ae747893a7fc4e7af
4
- data.tar.gz: d421e0b976d32ff63dab93132bf8f92976f69d8daa6cba66f9176ef8c46037fb
3
+ metadata.gz: 795ca593ca92570d9d4a54cbad72c18c32574d1190b37d002acb22f0347814e0
4
+ data.tar.gz: 585a9de965e98cc5f7d847357db1db8ce2a52e1200cf17169dc109c4cda231b5
5
5
  SHA512:
6
- metadata.gz: 8151b4f86ae25ed540effe2aace8decc6696da3b158ecd692f457b2938f5c20ba29f4d7768cb98214b6edb59f2779e33b0bb12b989c38e7837474d8c3b5fb0b0
7
- data.tar.gz: e935c7be37e01a45e69506a8753bb179610366053418d7d346734ea065987ff662e9889d3c6e7df6b8b697de01d0d7a4f908738e5e02284e64982cfab849686d
6
+ metadata.gz: d27b2e9fa7e9893cc43039c37c884e21c1e7945974d3614a907818453e99b88d0cd37ab68d43058e01303f3d6bc6966f51edeee009a712994ad40b754660a4d7
7
+ data.tar.gz: 1b6ddc2ab11f82cc6275d9ea26aa57800ebcfc2a2e7a84612894bb61b17a879e1b83eac20dff722091fab596eeb04912110d4bb70dbcce924771b189c218894f
data/README.markdown CHANGED
@@ -23,9 +23,11 @@ For manual evaluation of Ruby code and ERB templates see demo.rb
23
23
  You can use the ActionView template handlers by registering them, e.g., in
24
24
  a config/initializer file like this:
25
25
 
26
- # in config/intializer/safemode_tempate_handlers.rb
27
- ActionView::Template.register_template_handler :serb, ActionView::TemplateHandlers::SafeErb
28
- ActionView::Template.register_template_handler :haml, ActionView::TemplateHandlers::SafeHaml
26
+ ```ruby
27
+ # in config/intializer/safemode_tempate_handlers.rb
28
+ ActionView::Template.register_template_handler :serb, ActionView::TemplateHandlers::SafeErb
29
+ ActionView::Template.register_template_handler :haml, ActionView::TemplateHandlers::SafeHaml
30
+ ```
29
31
 
30
32
  If you register the ERB template handler for the file extension :erb be aware
31
33
  that this most probably will break when your application tries to render an
@@ -36,11 +38,13 @@ You will then have to "whitelist" all method calls to the objects that are
36
38
  registered as template variables by explicitely allowing access to them. You
37
39
  can do that by defining a Safemode::Jail class for your classes, like so:
38
40
 
39
- class User
40
- class Jail < Safemode::Jail
41
- allow :name
42
- end
43
- end
41
+ ```ruby
42
+ class User
43
+ class Jail < Safemode::Jail
44
+ allow :name
45
+ end
46
+ end
47
+ ```
44
48
 
45
49
  This will allow your template users to access the name method on your User
46
50
  objects.
@@ -63,12 +67,6 @@ Requires the gems:
63
67
  * RubyParser
64
68
  * Ruby2Ruby
65
69
 
66
- As of writing RubyParser alters StringIO and thus breaks usage with Rails.
67
- See http://www.zenspider.com/pipermail/parsetree/2008-April/000026.html
68
-
69
- A patch is included that fixes this issue and can be applied to RubyParser.
70
- See lib/ruby\_parser\_string\_io\_patch.diff
71
-
72
70
  ### Credits
73
71
 
74
72
  * Sven Fuchs - Initial Maintainer
@@ -6,7 +6,7 @@ module ActionView
6
6
  class SafeHaml < TemplateHandler
7
7
  include Compilable rescue nil # does not exist prior Rails 2.1
8
8
  extend SafemodeHandler
9
-
9
+
10
10
  def self.line_offset
11
11
  3
12
12
  end
@@ -5,23 +5,23 @@ module ActionView
5
5
  def valid_assigns(assigns)
6
6
  assigns = assigns.reject{|key, value| skip_assigns.include?(key) }
7
7
  end
8
-
8
+
9
9
  def delegate_methods(view)
10
- [ :render, :params, :flash ] +
11
- helper_methods(view) +
10
+ [ :render, :params, :flash ] +
11
+ helper_methods(view) +
12
12
  ActionController::Routing::Routes.named_routes.helpers
13
13
  end
14
14
 
15
15
  def helper_methods(view)
16
16
  view.class.included_modules.collect {|m| m.instance_methods(false) }.flatten.map(&:to_sym)
17
17
  end
18
-
18
+
19
19
  def skip_assigns
20
20
  [ "_cookies", "_flash", "_headers", "_params", "_request",
21
21
  "_response", "_session", "before_filter_chain_aborted",
22
22
  "ignore_missing_templates", "logger", "request_origin",
23
23
  "template", "template_class", "url", "variables_added",
24
- "view_paths" ]
24
+ "view_paths" ]
25
25
  end
26
26
  end
27
27
  end
data/lib/haml/safemode.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'haml'
2
2
 
3
- module Haml
3
+ module Haml
4
4
  class Buffer
5
5
  class Jail < Safemode::Jail
6
6
  allow :push_script, :push_text, :_hamlout, :open_tag
@@ -8,33 +8,33 @@ module Haml
8
8
  end
9
9
  end
10
10
 
11
- module Haml
11
+ module Haml
12
12
  class Engine
13
- def precompile_for_safemode(filename, ignore_assigns = [], delegate_methods = [])
13
+ def precompile_for_safemode(filename, ignore_assigns = [], delegate_methods = [])
14
14
  @precompiled.gsub!('\\','\\\\\\') # backslashes would disappear in compile_template/modul_eval, so we escape them
15
-
16
- <<-CODE
15
+
16
+ <<-CODE
17
17
  buffer = Haml::Buffer.new(#{options_for_buffer.inspect})
18
18
  local_assigns = local_assigns.merge :_hamlout => buffer
19
-
19
+
20
20
  handler = ActionView::TemplateHandlers::SafeHaml
21
21
  assigns = handler.valid_assigns(@template.assigns)
22
22
  methods = handler.delegate_methods(self)
23
23
  code = %Q(#{code});
24
-
24
+
25
25
  box = Safemode::Box.new(self, methods, #{filename.inspect}, 0)
26
- box.eval(code, assigns, local_assigns, &lambda{ yield })
27
- buffer.buffer
26
+ box.eval(code, assigns, local_assigns, &lambda{ yield })
27
+ buffer.buffer
28
28
  CODE
29
29
 
30
30
  # preamble = "buffer = Haml::Buffer.new(#{options_for_buffer.inspect})
31
31
  # local_assigns = local_assigns.merge :_hamlout => buffer
32
32
  # assigns = @template.assigns.reject{|key, value| #{ignore_assigns.inspect}.include?(key) };".gsub("\n", ';')
33
- #
33
+ #
34
34
  # postamble = "box = Safemode::Box.new(self, #{delegate_methods.inspect})
35
35
  # box.eval(code, assigns, local_assigns, &lambda{ yield })
36
36
  # buffer.buffer".gsub("\n", ';')
37
- #
37
+ #
38
38
  # preamble + "code = %Q(#{@precompiled});" + postamble
39
39
  end
40
40
  end
@@ -1,12 +1,12 @@
1
- module Kernel
1
+ module Kernel
2
2
  def silently(&blk)
3
3
  old_verbose, $VERBOSE = $VERBOSE, nil
4
4
  yield
5
5
  $VERBOSE = old_verbose
6
- end
6
+ end
7
7
  end
8
8
 
9
- class Module
9
+ class Module
10
10
  def undef_methods(*methods)
11
11
  methods.each { |name| undef_method(name) }
12
12
  end
@@ -29,7 +29,7 @@ end
29
29
  # Safemode.jail collect{ |obj| obj.to_jail }
30
30
  # end
31
31
  # end
32
- #
32
+ #
33
33
  # class Hash
34
34
  # def to_jail
35
35
  # hash = {}
@@ -1,19 +1,19 @@
1
1
  module Safemode
2
2
  class Error < RuntimeError; end
3
-
3
+
4
4
  class SecurityError < Error
5
5
  @@types = { :const => 'constant',
6
6
  :xstr => 'shell command',
7
7
  :fcall => 'method',
8
8
  :vcall => 'method',
9
9
  :gvar => 'global variable' }
10
-
10
+
11
11
  def initialize(type, value = nil)
12
12
  type = @@types[type] if @@types.include?(type)
13
13
  super "Safemode doesn't allow to access '#{type}'" + (value ? " on #{value}" : '')
14
14
  end
15
15
  end
16
-
16
+
17
17
  class NoMethodError < Error
18
18
  def initialize(method, jail, source = nil)
19
19
  super "undefined method '#{method}' for #{jail}" + (source ? " (#{source})" : '')
data/lib/safemode/jail.rb CHANGED
@@ -12,7 +12,7 @@ module Safemode
12
12
  @source.to_s
13
13
  end
14
14
 
15
- def method_missing(method, *args, &block)
15
+ def method_missing(method, *args, **kwargs, &block)
16
16
  if @source.is_a?(Class)
17
17
  unless self.class.allowed_class_method?(method)
18
18
  raise Safemode::NoMethodError.new(".#{method}", self.class.name, @source.name)
@@ -28,7 +28,7 @@ module Safemode
28
28
  # don't need to jail objects returned from a jail. Doing so would provide
29
29
  # "double" protection, but it also would break using a return value in an if
30
30
  # statement, passing them to a Rails helper etc.
31
- @source.send(method, *args, &block)
31
+ @source.send(method, *args, **kwargs, &block)
32
32
  end
33
33
 
34
34
  def respond_to_missing?(method_name, include_private = false)
@@ -26,19 +26,22 @@ module Safemode
26
26
  end
27
27
  end
28
28
 
29
- def jail(str, parentheses = false)
30
- str = parentheses ? "(#{str})." : "#{str}." if str
29
+ def jail(str, parentheses = false, safe_call: false)
30
+ str = if str
31
+ dot = safe_call ? "&." : "."
32
+ parentheses ? "(#{str})#{dot}" : "#{str}#{dot}"
33
+ end
31
34
  "#{str}to_jail"
32
35
  end
33
36
 
34
37
  # split up #process_call. see below ...
35
- def process_call(exp)
36
- exp.shift # remove ":call" symbol
37
- receiver = jail process_call_receiver(exp)
38
- name = exp.shift
39
- args = process_call_args(exp)
38
+ def process_call(exp, safe_call = false)
39
+ _, recv, name, *args = exp
40
40
 
41
- process_call_code(receiver, name, args)
41
+ receiver = jail(process_call_receiver(recv), safe_call: safe_call)
42
+ arguments = process_call_args(name, args)
43
+
44
+ process_call_code(receiver, name, arguments, safe_call)
42
45
  end
43
46
 
44
47
  def process_fcall(exp)
@@ -140,44 +143,67 @@ module Safemode
140
143
  # split up Ruby2Ruby#process_call monster method so we can hook into it
141
144
  # in a more readable manner
142
145
 
143
- def process_call_receiver(exp)
144
- receiver_node_type = exp.first.nil? ? nil : exp.first.first
145
- receiver = process exp.shift
146
- receiver = "(#{receiver})" if
147
- Ruby2Ruby::ASSIGN_NODES.include? receiver_node_type
146
+ def process_call_receiver(recv)
147
+ receiver_node_type = recv && recv.sexp_type
148
+ receiver = process recv
149
+ receiver = "(#{receiver})" if ASSIGN_NODES.include? receiver_node_type
148
150
  receiver
149
151
  end
150
152
 
151
- def process_call_args(exp)
152
- args = []
153
- while not exp.empty? do
154
- args_exp = exp.shift
155
- if args_exp && args_exp.first == :array # FIX
156
- processed = "#{process(args_exp)[1..-2]}"
157
- else
158
- processed = process args_exp
159
- end
160
- args << processed unless (processed.nil? or processed.empty?)
153
+ def process_call_args(name, args)
154
+ in_context :arglist do
155
+ max = args.size - 1
156
+ args = args.map.with_index { |arg, i|
157
+ arg_type = arg.sexp_type
158
+ is_empty_hash = arg == s(:hash)
159
+ arg = process arg
160
+
161
+ next if arg.empty?
162
+
163
+ strip_hash = (arg_type == :hash and
164
+ not BINARY.include? name and
165
+ not is_empty_hash and
166
+ (i == max or args[i + 1].sexp_type == :splat))
167
+ wrap_arg = Ruby2Ruby::ASSIGN_NODES.include? arg_type
168
+
169
+ arg = arg[2..-3] if strip_hash
170
+ arg = "(#{arg})" if wrap_arg
171
+
172
+ arg
173
+ }.compact
161
174
  end
162
- args.empty? ? nil : args.join(", ")
163
175
  end
164
176
 
165
- def process_call_code(receiver, name, args)
177
+ def process_call_code(receiver, name, args, safe_call)
166
178
  case name
167
- when :<=>, :==, "!=".to_sym, :<, :>, :<=, :>=, :-, :+, :*, :/, :%, :<<, :>>, :** then
168
- "(#{receiver} #{name} #{args})"
179
+ when *BINARY then
180
+ if safe_call
181
+ "#{receiver}&.#{name}(#{args.join(", ")})"
182
+ elsif args.length > 1
183
+ "#{receiver}.#{name}(#{args.join(", ")})"
184
+ else
185
+ "(#{receiver} #{name} #{args.join(", ")})"
186
+ end
169
187
  when :[] then
170
- "#{receiver}[#{args}]"
188
+ receiver ||= "self"
189
+ "#{receiver}[#{args.join(", ")}]"
190
+ when :[]= then
191
+ receiver ||= "self"
192
+ rhs = args.pop
193
+ "#{receiver}[#{args.join(", ")}] = #{rhs}"
194
+ when :"!" then
195
+ "(not #{receiver})"
171
196
  when :"-@" then
172
197
  "-#{receiver}"
173
198
  when :"+@" then
174
199
  "+#{receiver}"
175
200
  else
176
- unless receiver.nil? then
177
- "#{receiver}.#{name}#{args ? "(#{args})" : args}"
178
- else
179
- "#{name}#{args ? "(#{args})" : args}"
180
- end
201
+ args = nil if args.empty?
202
+ args = "(#{args.join(", ")})" if args
203
+ receiver = "#{receiver}." if receiver and not safe_call
204
+ receiver = "#{receiver}&." if receiver and safe_call
205
+
206
+ "#{receiver}#{name}#{args}"
181
207
  end
182
208
  end
183
209
 
@@ -1,7 +1,7 @@
1
1
  module Safemode
2
2
  class Scope < Blankslate
3
3
  def initialize(delegate = nil, delegate_methods = [], instance_vars: {}, locals: {}, &block)
4
- @delegate = delegate
4
+ @delegate = delegate
5
5
  @delegate_methods = delegate_methods
6
6
  @locals = symbolize_keys(locals) # why can't I just pull them to local scope in the same way like instance_vars?
7
7
  instance_vars = symbolize_keys(instance_vars)
@@ -13,46 +13,52 @@ module Safemode
13
13
  def get_binding
14
14
  @binding
15
15
  end
16
-
16
+
17
17
  def to_jail
18
18
  self
19
19
  end
20
-
20
+
21
21
  def puts(*args)
22
22
  print args.to_s + "\n"
23
23
  end
24
24
 
25
- def print(*args)
25
+ def print(*args)
26
26
  @_safemode_output += args.to_s
27
27
  end
28
-
28
+
29
29
  def output
30
30
  @_safemode_output
31
31
  end
32
32
 
33
- def method_missing(method, *args, &block)
33
+ def method_missing(method, *args, **kwargs, &block)
34
34
  if @locals.has_key?(method)
35
35
  @locals[method]
36
36
  elsif @delegate_methods.include?(method)
37
- @delegate.send method, *unjail_args(args), &block
37
+ @delegate.send method, *unjail_args(args), **unjail_kwargs(kwargs), &block
38
38
  else
39
39
  raise Safemode::SecurityError.new(method, "#<Safemode::ScopeObject>")
40
40
  end
41
41
  end
42
-
42
+
43
43
  private
44
-
44
+
45
45
  def symbolize_keys(hash)
46
- hash.inject({}) do |hash, (key, value)|
46
+ hash.inject({}) do |hash, (key, value)|
47
47
  hash[key.to_s.intern] = value
48
48
  hash
49
49
  end
50
50
  end
51
-
51
+
52
+ def unjail(arg)
53
+ arg.class.name.end_with?('::Jail') ? arg.instance_variable_get(:@source) : arg
54
+ end
55
+
52
56
  def unjail_args(args)
53
- args.collect do |arg|
54
- arg.class.name =~ /::Jail$/ ? arg.instance_variable_get(:@source) : arg
55
- end
57
+ args.collect { |arg| unjail(arg) }
58
+ end
59
+
60
+ def unjail_kwargs(kwargs)
61
+ kwargs.map { |key, value| [unjail(key), unjail(value)] }.to_h
56
62
  end
57
63
  end
58
64
  end
data/lib/safemode.rb CHANGED
@@ -26,7 +26,7 @@ module Safemode
26
26
  def jail(obj)
27
27
  find_jail_class(obj.is_a?(Class) ? obj : obj.class).new obj
28
28
  end
29
-
29
+
30
30
  def find_jail_class(klass)
31
31
  while klass != Object
32
32
  return klass.const_get('Jail') if klass.const_defined?('Jail')
@@ -35,16 +35,16 @@ module Safemode
35
35
  Jail
36
36
  end
37
37
  end
38
-
38
+
39
39
  define_core_jail_classes
40
-
40
+
41
41
  class Box
42
42
  def initialize(delegate = nil, delegate_methods = [], filename = nil, line = nil)
43
43
  @delegate = delegate
44
44
  @delegate_methods = delegate_methods
45
45
  @filename = filename
46
46
  @line = line
47
- end
47
+ end
48
48
 
49
49
  def eval(code, assigns = {}, locals = {}, &block)
50
50
  code = Parser.jail(code)
data/safemode.gemspec CHANGED
@@ -4,7 +4,7 @@ require 'date'
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "safemode".freeze
7
- s.version = "1.3.8"
7
+ s.version = "1.5.0"
8
8
  s.date = Date.today
9
9
 
10
10
  s.summary = "A library for safe evaluation of Ruby code based on ParseTree/RubyParser and Ruby2Ruby"
@@ -36,8 +36,6 @@ Gem::Specification.new do |s|
36
36
  "lib/action_view/template_handlers/safe_haml.rb",
37
37
  "lib/action_view/template_handlers/safemode_handler.rb",
38
38
  "lib/haml/safemode.rb",
39
- "lib/ruby_parser_string_io_patch.diff",
40
- "lib/rubyparser_bug.rb",
41
39
  "lib/safemode.rb",
42
40
  "lib/safemode/blankslate.rb",
43
41
  "lib/safemode/core_ext.rb",
@@ -54,7 +52,7 @@ Gem::Specification.new do |s|
54
52
  "test/test_safemode_parser.rb"
55
53
  ]
56
54
 
57
- s.required_ruby_version = ">= 2.5", "< 4"
55
+ s.required_ruby_version = ">= 2.7", "< 3.2"
58
56
 
59
57
  s.add_runtime_dependency "ruby2ruby", ">= 2.4.0"
60
58
  s.add_runtime_dependency "ruby_parser", ">= 3.10.1"
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
3
  class TestERBEval < Test::Unit::TestCase
4
4
  include TestHelper
5
-
5
+
6
6
  def setup
7
7
  @box = Safemode::Box.new
8
8
  @locals = { :article => Article.new }
@@ -18,45 +18,45 @@ class TestERBEval < Test::Unit::TestCase
18
18
  assert_nothing_raised{ @box.eval code }
19
19
  end
20
20
  end
21
-
21
+
22
22
  def test_should_turn_assigns_to_jails
23
23
  assert_raise_no_method "@article.system", @assigns, &@erb_parse
24
24
  end
25
-
25
+
26
26
  def test_should_turn_locals_to_jails
27
27
  code = @erb_parse.call "article.system"
28
28
  assert_raise(Safemode::NoMethodError){ @box.eval code, {}, @locals }
29
29
  end
30
-
30
+
31
31
  def test_should_allow_method_access_on_assigns
32
32
  code = @erb_parse.call "@article.title"
33
33
  assert_nothing_raised{ @box.eval code, @assigns }
34
34
  end
35
-
35
+
36
36
  def test_should_allow_method_access_on_locals
37
37
  code = @erb_parse.call "article.title"
38
38
  assert_nothing_raised{ @box.eval code, {}, @locals }
39
39
  end
40
-
40
+
41
41
  def test_should_not_raise_on_if_using_return_values
42
42
  code = @erb_parse.call "if @article.is_article?\n 1\n end"
43
43
  assert_nothing_raised{ @box.eval code, @assigns }
44
44
  end
45
-
45
+
46
46
  def test_should_work_with_if_using_return_values
47
47
  code = @erb_parse.call "if @article.is_article? then 1 end"
48
48
  assert_equal @box.eval(code, @assigns), "1" # ERB calls to_s on the result of the if block
49
49
  end
50
-
50
+
51
51
  def test__FILE__should_not_render_filename
52
52
  code = @erb_parse.call "__FILE__"
53
53
  assert_equal '(string)', @box.eval(code)
54
54
  end
55
-
55
+
56
56
  def test_interpolated_xstr_should_raise_security
57
57
  assert_raise_security '"#{`ls -a`}"'
58
- end
59
-
58
+ end
59
+
60
60
  TestHelper.no_method_error_raising_calls.each do |call|
61
61
  call.gsub!('"', '\\\\"')
62
62
  class_eval %Q(
@@ -65,7 +65,7 @@ class TestERBEval < Test::Unit::TestCase
65
65
  end
66
66
  )
67
67
  end
68
-
68
+
69
69
  TestHelper.security_error_raising_calls.each do |call|
70
70
  call.gsub!('"', '\\\\"')
71
71
  class_eval %Q(
@@ -73,6 +73,6 @@ class TestERBEval < Test::Unit::TestCase
73
73
  assert_raise_security "#{call}", @assigns, @locals
74
74
  end
75
75
  )
76
- end
76
+ end
77
77
 
78
78
  end
data/test/test_helper.rb CHANGED
@@ -110,8 +110,12 @@ class Article
110
110
  Comment
111
111
  end
112
112
 
113
- def method_missing(method, *args, &block)
114
- super(method, *args, &block)
113
+ def method_with_kwargs(a_keyword: false)
114
+ a_keyword
115
+ end
116
+
117
+ def method_missing(method, *args, **kwargs, &block)
118
+ super
115
119
  end
116
120
  end
117
121
 
@@ -144,7 +148,7 @@ class Comment
144
148
  end
145
149
 
146
150
  class Article::Jail < Safemode::Jail
147
- allow :title, :comments, :is_article?, :comment_class
151
+ allow :title, :comments, :is_article?, :comment_class, :method_with_kwargs
148
152
 
149
153
  def author_name
150
154
  "this article's author name"
data/test/test_jail.rb CHANGED
@@ -24,6 +24,16 @@ class TestJail < Test::Unit::TestCase
24
24
  assert_equal "Article::Jail", @article.class.name
25
25
  end
26
26
 
27
+ def test_sending_of_kwargs_works
28
+ assert @article.method_with_kwargs(a_keyword: true)
29
+ end
30
+
31
+ def test_sending_to_method_missing
32
+ assert_raise_with_message(Safemode::NoMethodError, /#no_such_method/) do
33
+ @article.no_such_method('arg', key: 'value')
34
+ end
35
+ end
36
+
27
37
  def test_jail_instances_should_have_limited_methods
28
38
  expected = ["class", "method_missing", "methods", "respond_to?", "to_jail", "to_s", "instance_variable_get"]
29
39
  objects.each do |object|
@@ -17,6 +17,10 @@ class TestSafemodeEval < Test::Unit::TestCase
17
17
  end
18
18
  end
19
19
 
20
+ def test_safe_navigation_operator
21
+ assert_equal "1", @box.eval('x = 1; x&.to_s')
22
+ end
23
+
20
24
  def test_unary_operators_on_instances_of_boolean_vars
21
25
  assert @box.eval('not false')
22
26
  assert @box.eval('!false')
@@ -84,6 +88,16 @@ class TestSafemodeEval < Test::Unit::TestCase
84
88
  assert_raise_security "self.bind('an arg')"
85
89
  end
86
90
 
91
+ def test_sending_of_kwargs_works
92
+ assert @box.eval("@article.method_with_kwargs(a_keyword: true)", @assigns)
93
+ end
94
+
95
+ def test_sending_to_method_missing
96
+ assert_raise_with_message(Safemode::NoMethodError, /#no_such_method/) do
97
+ @box.eval("@article.no_such_method('arg', key: 'value')", @assigns)
98
+ end
99
+ end
100
+
87
101
  TestHelper.no_method_error_raising_calls.each do |call|
88
102
  call.gsub!('"', '\\\\"')
89
103
  class_eval %Q(
@@ -4,27 +4,55 @@ class TestSafemodeParser < Test::Unit::TestCase
4
4
  def test_vcall_should_be_jailed
5
5
  assert_jailed 'to_jail.a.to_jail.class', 'a.class'
6
6
  end
7
-
7
+
8
8
  def test_call_should_be_jailed
9
9
  assert_jailed '(1.to_jail + 1).to_jail.class', '(1 + 1).class'
10
10
  end
11
-
11
+
12
12
  def test_estr_should_be_jailed
13
13
  assert_jailed '"#{1.to_jail.class}"', '"#{1.class}"'
14
14
  end
15
-
15
+
16
16
  def test_if_should_be_usable_for_erb
17
17
  assert_jailed "if true then\n 1\nend", "if true\n 1\n end"
18
18
  end
19
-
19
+
20
20
  def test_if_else_should_be_usable_for_erb
21
21
  assert_jailed "if true then\n 1\n else\n2\nend", "if true\n 1\n else\n2\n end"
22
22
  end
23
-
23
+
24
24
  def test_ternary_should_be_usable_for_erb
25
25
  assert_jailed "if true then\n 1\n else\n2\nend", "true ? 1 : 2"
26
26
  end
27
27
 
28
+ def test_call_with_shorthand
29
+ unsafe = <<~UNSAFE
30
+ a_keyword = true
31
+ @article.method_with_kwargs(a_keyword:)
32
+ UNSAFE
33
+ jailed = <<~JAILED
34
+ a_keyword = true
35
+ @article.to_jail.method_with_kwargs(a_keyword:)
36
+ JAILED
37
+ assert_jailed jailed, unsafe
38
+ end
39
+
40
+ def test_call_with_complex_args
41
+ unsafe = "kwargs = { b_keyword: false }; @article.method_with_kwargs('positional', a_keyword: true, **kwargs)"
42
+ jailed = "kwargs = { :b_keyword => false }\n@article.to_jail.method_with_kwargs(\"positional\", :a_keyword => true, **kwargs)\n"
43
+ assert_jailed jailed, unsafe
44
+ end
45
+
46
+ def test_safe_call_simple
47
+ assert_jailed '@article&.to_jail&.method', '@article&.method'
48
+ end
49
+
50
+ def test_safe_call_with_complex_args
51
+ unsafe = "kwargs = { b_keyword: false }; @article&.method_with_kwargs('positional', a_keyword: true, **kwargs)"
52
+ jailed = "kwargs = { :b_keyword => false }\n@article&.to_jail&.method_with_kwargs(\"positional\", :a_keyword => true, **kwargs)\n"
53
+ assert_jailed jailed, unsafe
54
+ end
55
+
28
56
  def test_output_buffer_should_be_assignable
29
57
  assert_nothing_raised do
30
58
  jail('@output_buffer = ""')
@@ -38,11 +66,11 @@ class TestSafemodeParser < Test::Unit::TestCase
38
66
  end
39
67
 
40
68
  private
41
-
69
+
42
70
  def assert_jailed(expected, code)
43
71
  assert_equal expected.gsub(' ', ''), jail(code).gsub(' ', '')
44
- end
45
-
72
+ end
73
+
46
74
  def jail(code)
47
75
  Safemode::Parser.jail(code)
48
76
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safemode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.8
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -10,10 +10,10 @@ authors:
10
10
  - Kingsley Hendrickse
11
11
  - Ohad Levy
12
12
  - Dmitri Dolguikh
13
- autorequire:
13
+ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2023-07-11 00:00:00.000000000 Z
16
+ date: 2024-03-19 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: ruby2ruby
@@ -115,7 +115,7 @@ dependencies:
115
115
  version: '0'
116
116
  description: A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby.
117
117
  Provides Rails ActionView template handlers for ERB and Haml.
118
- email:
118
+ email:
119
119
  executables: []
120
120
  extensions: []
121
121
  extra_rdoc_files:
@@ -132,8 +132,6 @@ files:
132
132
  - lib/action_view/template_handlers/safe_haml.rb
133
133
  - lib/action_view/template_handlers/safemode_handler.rb
134
134
  - lib/haml/safemode.rb
135
- - lib/ruby_parser_string_io_patch.diff
136
- - lib/rubyparser_bug.rb
137
135
  - lib/safemode.rb
138
136
  - lib/safemode/blankslate.rb
139
137
  - lib/safemode/core_ext.rb
@@ -152,7 +150,7 @@ homepage: https://github.com/svenfuchs/safemode
152
150
  licenses:
153
151
  - MIT
154
152
  metadata: {}
155
- post_install_message:
153
+ post_install_message:
156
154
  rdoc_options: []
157
155
  require_paths:
158
156
  - lib
@@ -160,18 +158,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
158
  requirements:
161
159
  - - ">="
162
160
  - !ruby/object:Gem::Version
163
- version: '2.5'
161
+ version: '2.7'
164
162
  - - "<"
165
163
  - !ruby/object:Gem::Version
166
- version: '4'
164
+ version: '3.2'
167
165
  required_rubygems_version: !ruby/object:Gem::Requirement
168
166
  requirements:
169
167
  - - ">="
170
168
  - !ruby/object:Gem::Version
171
169
  version: '0'
172
170
  requirements: []
173
- rubygems_version: 3.1.6
174
- signing_key:
171
+ rubygems_version: 3.4.10
172
+ signing_key:
175
173
  specification_version: 4
176
174
  summary: A library for safe evaluation of Ruby code based on ParseTree/RubyParser
177
175
  and Ruby2Ruby
@@ -1,194 +0,0 @@
1
- --- lib/ruby_lexer.rb 2008-04-27 01:07:24.000000000 +0200
2
- +++ lib/ruby_lexer.rb 2008-04-27 01:07:03.000000000 +0200
3
- @@ -45,7 +45,7 @@
4
- raise "bad val: #{str.inspect}" unless String === str
5
-
6
- self.file = file
7
- - self.lexer.src = StringIO.new(str)
8
- + self.lexer.src = RubyParser::StringIO.new(str)
9
-
10
- @yydebug = ENV.has_key? 'DEBUG'
11
-
12
- @@ -2604,104 +2604,106 @@
13
- end
14
- end
15
-
16
- -class StringIO # HACK: everything in here is a hack
17
- - attr_accessor :begin_of_line, :was_begin_of_line
18
- - alias :begin_of_line? :begin_of_line
19
- - alias :read_all :read
20
- +class RubyParser
21
- + class StringIO < StringIO # HACK: everything in here is a hack
22
- + attr_accessor :begin_of_line, :was_begin_of_line
23
- + alias :begin_of_line? :begin_of_line
24
- + alias :read_all :read
25
-
26
- - alias :old_initialize :initialize
27
- + alias :old_initialize :initialize
28
-
29
- - def initialize(*args)
30
- - self.begin_of_line = true
31
- - self.was_begin_of_line = false
32
- - old_initialize(*args)
33
- - @original_string = self.string.dup
34
- - end
35
- + def initialize(*args)
36
- + self.begin_of_line = true
37
- + self.was_begin_of_line = false
38
- + old_initialize(*args)
39
- + @original_string = self.string.dup
40
- + end
41
-
42
- - def rest
43
- - self.string[self.pos..-1]
44
- - end
45
- + def rest
46
- + self.string[self.pos..-1]
47
- + end
48
-
49
- - def current_line # HAHA fuck you
50
- - @original_string[0..self.pos][/\A.*__LINE__/m].split(/\n/).size
51
- - end
52
- + def current_line # HAHA fuck you
53
- + @original_string[0..self.pos][/\A.*__LINE__/m].split(/\n/).size
54
- + end
55
-
56
- - def read
57
- - c = self.getc
58
- + def read
59
- + c = self.getc
60
-
61
- - if c == ?\r then
62
- - d = self.getc
63
- - self.ungetc d if d and d != ?\n
64
- - c = ?\n
65
- - end
66
- + if c == ?\r then
67
- + d = self.getc
68
- + self.ungetc d if d and d != ?\n
69
- + c = ?\n
70
- + end
71
-
72
- - self.was_begin_of_line = self.begin_of_line
73
- - self.begin_of_line = c == ?\n
74
- - if c and c != 0 then
75
- - c.chr
76
- - else
77
- - ::RubyLexer::EOF
78
- + self.was_begin_of_line = self.begin_of_line
79
- + self.begin_of_line = c == ?\n
80
- + if c and c != 0 then
81
- + c.chr
82
- + else
83
- + ::RubyLexer::EOF
84
- + end
85
- end
86
- - end
87
-
88
- - def match_string term, indent=false # TODO: add case insensitivity, or just remove
89
- - buffer = []
90
- + def match_string term, indent=false # TODO: add case insensitivity, or just remove
91
- + buffer = []
92
-
93
- - if indent
94
- - while c = self.read do
95
- - if c !~ /\s/ or c == "\n" or c == "\r" then
96
- - self.unread c
97
- - break
98
- + if indent
99
- + while c = self.read do
100
- + if c !~ /\s/ or c == "\n" or c == "\r" then
101
- + self.unread c
102
- + break
103
- + end
104
- + buffer << c
105
- end
106
- - buffer << c
107
- end
108
- - end
109
-
110
- - term.each_byte do |c2|
111
- - c = self.read
112
- - c = self.read if c and c == "\r"
113
- - buffer << c
114
- - if c and c2 != c[0] then
115
- - self.unread_many buffer.join # HACK omg
116
- - return false
117
- + term.each_byte do |c2|
118
- + c = self.read
119
- + c = self.read if c and c == "\r"
120
- + buffer << c
121
- + if c and c2 != c[0] then
122
- + self.unread_many buffer.join # HACK omg
123
- + return false
124
- + end
125
- end
126
- +
127
- + return true
128
- end
129
-
130
- - return true
131
- - end
132
- + def read_line
133
- + self.begin_of_line = true
134
- + self.was_begin_of_line = false
135
- + gets.sub(/\r\n?$/, "\n") # HACK
136
- + end
137
-
138
- - def read_line
139
- - self.begin_of_line = true
140
- - self.was_begin_of_line = false
141
- - gets.sub(/\r\n?$/, "\n") # HACK
142
- - end
143
- -
144
- - def peek expected = nil # FIX: barf
145
- - c = self.getc
146
- - return RubyLexer::EOF if c.nil?
147
- - self.ungetc c if c
148
- - c = c.chr if c
149
- - if expected then
150
- - c == expected
151
- - else
152
- - c
153
- + def peek expected = nil # FIX: barf
154
- + c = self.getc
155
- + return RubyLexer::EOF if c.nil?
156
- + self.ungetc c if c
157
- + c = c.chr if c
158
- + if expected then
159
- + c == expected
160
- + else
161
- + c
162
- + end
163
- end
164
- - end
165
-
166
- - def unread(c)
167
- - return if c.nil? # UGH
168
- + def unread(c)
169
- + return if c.nil? # UGH
170
-
171
- - # HACK: only depth is 2... who cares? really I want to remove all of this
172
- - self.begin_of_line = self.was_begin_of_line || true
173
- - self.was_begin_of_line = nil
174
- + # HACK: only depth is 2... who cares? really I want to remove all of this
175
- + self.begin_of_line = self.was_begin_of_line || true
176
- + self.was_begin_of_line = nil
177
-
178
- - c = c[0] if String === c
179
- - self.ungetc c
180
- - end
181
- + c = c[0] if String === c
182
- + self.ungetc c
183
- + end
184
-
185
- - def unread_many str
186
- - str.split(//).reverse.each do |c|
187
- - unread c
188
- + def unread_many str
189
- + str.split(//).reverse.each do |c|
190
- + unread c
191
- + end
192
- end
193
- end
194
- end
@@ -1,8 +0,0 @@
1
- require 'rubygems'
2
- require 'ruby2ruby'
3
-
4
- sexp = ParseTree.translate('a')
5
- puts Ruby2Ruby.new.process(sexp)
6
-
7
- # should work, but yields:
8
- # UnknownNodeError: Bug! Unknown node-type :vcall to Ruby2Ruby