safemode 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -13
- data/README.markdown +12 -16
- data/Rakefile +1 -23
- data/lib/action_view/template_handlers/safe_haml.rb +1 -1
- data/lib/action_view/template_handlers/safemode_handler.rb +5 -5
- data/lib/haml/safemode.rb +11 -11
- data/lib/safemode/core_ext.rb +4 -4
- data/lib/safemode/exceptions.rb +3 -3
- data/lib/safemode/jail.rb +2 -2
- data/lib/safemode/parser.rb +59 -33
- data/lib/safemode/scope.rb +26 -20
- data/lib/safemode.rb +10 -9
- data/safemode.gemspec +28 -51
- data/test/test_erb_eval.rb +13 -13
- data/test/test_helper.rb +7 -3
- data/test/test_jail.rb +10 -0
- data/test/test_safemode_eval.rb +18 -0
- data/test/test_safemode_parser.rb +36 -8
- metadata +13 -27
- data/VERSION +0 -1
- data/lib/ruby_parser_string_io_patch.diff +0 -194
- data/lib/rubyparser_bug.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51fa5ec1cd2377a9738d9298baa27c691fe9849a8a9b74532f1f77ffa938b237
|
4
|
+
data.tar.gz: b8fbeefe3b404c4910498cdcacfd4e6dfa06f6cf5369fa7244152f8e3a5943af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c22cf3bf164821108828112f05b10bc3e41aa2f123d355803483788dcb9b2ec105bebaabe34f56801543b4e2cbc423a0035ac8f6a81d20bf3f69bd70a91dc1c5
|
7
|
+
data.tar.gz: e010c81530a3db33ab3e5f9fa0edbc46a12daeb60791cc9340185b691f970e7e993cba5a29cd4e5e1aa40855493de66545f2a4624ebaa10edd53da13e51e18e5
|
data/Gemfile
CHANGED
@@ -2,16 +2,4 @@
|
|
2
2
|
|
3
3
|
source 'http://rubygems.org'
|
4
4
|
|
5
|
-
|
6
|
-
gem 'ruby_parser', '>= 3.10.1'
|
7
|
-
gem 'sexp_processor', '>= 4.10.0'
|
8
|
-
|
9
|
-
# Add dependencies to develop your gem here.
|
10
|
-
# Include everything needed to run rake, tests, features, etc.
|
11
|
-
group :development do
|
12
|
-
gem 'jeweler'
|
13
|
-
gem 'rake'
|
14
|
-
gem 'rdoc', '~> 3.12'
|
15
|
-
gem 'simplecov'
|
16
|
-
gem 'test-unit'
|
17
|
-
end
|
5
|
+
gemspec
|
data/README.markdown
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
A library for safe evaluation of Ruby code based on RubyParser and
|
4
4
|
Ruby2Ruby. Provides Rails ActionView template handlers for ERB and Haml.
|
5
5
|
|
6
|
-
[](https://travis-ci.org/svenfuchs/safemode)
|
7
|
-
|
8
6
|
### Word of warning
|
9
7
|
|
10
8
|
This library is still highly experimental. Only use it at your own risk for
|
@@ -25,9 +23,11 @@ For manual evaluation of Ruby code and ERB templates see demo.rb
|
|
25
23
|
You can use the ActionView template handlers by registering them, e.g., in
|
26
24
|
a config/initializer file like this:
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
+
```
|
31
31
|
|
32
32
|
If you register the ERB template handler for the file extension :erb be aware
|
33
33
|
that this most probably will break when your application tries to render an
|
@@ -38,11 +38,13 @@ You will then have to "whitelist" all method calls to the objects that are
|
|
38
38
|
registered as template variables by explicitely allowing access to them. You
|
39
39
|
can do that by defining a Safemode::Jail class for your classes, like so:
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
```ruby
|
42
|
+
class User
|
43
|
+
class Jail < Safemode::Jail
|
44
|
+
allow :name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
46
48
|
|
47
49
|
This will allow your template users to access the name method on your User
|
48
50
|
objects.
|
@@ -65,12 +67,6 @@ Requires the gems:
|
|
65
67
|
* RubyParser
|
66
68
|
* Ruby2Ruby
|
67
69
|
|
68
|
-
As of writing RubyParser alters StringIO and thus breaks usage with Rails.
|
69
|
-
See http://www.zenspider.com/pipermail/parsetree/2008-April/000026.html
|
70
|
-
|
71
|
-
A patch is included that fixes this issue and can be applied to RubyParser.
|
72
|
-
See lib/ruby\_parser\_string\_io\_patch.diff
|
73
|
-
|
74
70
|
### Credits
|
75
71
|
|
76
72
|
* Sven Fuchs - Initial Maintainer
|
data/Rakefile
CHANGED
@@ -19,28 +19,6 @@ end
|
|
19
19
|
end
|
20
20
|
require 'rake'
|
21
21
|
|
22
|
-
require 'jeweler'
|
23
|
-
Jeweler::Tasks.new do |gem|
|
24
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
25
|
-
gem.name = "safemode"
|
26
|
-
gem.homepage = "https://github.com/svenfuchs/safemode"
|
27
|
-
gem.license = "MIT"
|
28
|
-
gem.summary = %Q{A library for safe evaluation of Ruby code based on ParseTree/RubyParser and Ruby2Ruby}
|
29
|
-
gem.description = %Q{A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby. Provides Rails ActionView template handlers for ERB and Haml.}
|
30
|
-
gem.email = "ohadlevy@gmail.com"
|
31
|
-
gem.authors = [
|
32
|
-
"Sven Fuchs",
|
33
|
-
"Peter Cooper",
|
34
|
-
"Matthias Viehweger",
|
35
|
-
"Kingsley Hendrickse",
|
36
|
-
"Ohad Levy",
|
37
|
-
"Dmitri Dolguikh",
|
38
|
-
]
|
39
|
-
gem.files.exclude '.travis.yml'
|
40
|
-
# dependencies defined in Gemfile
|
41
|
-
end
|
42
|
-
Jeweler::RubygemsDotOrgTasks.new
|
43
|
-
|
44
22
|
require 'rake/testtask'
|
45
23
|
Rake::TestTask.new(:test) do |test|
|
46
24
|
test.libs << 'lib' << 'test'
|
@@ -58,7 +36,7 @@ task :default => :test
|
|
58
36
|
|
59
37
|
require 'rdoc/task'
|
60
38
|
Rake::RDocTask.new do |rdoc|
|
61
|
-
version =
|
39
|
+
version = Gem::Specification.find_by_name('safemode').version
|
62
40
|
|
63
41
|
rdoc.rdoc_dir = 'rdoc'
|
64
42
|
rdoc.title = "safemode #{version}"
|
@@ -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
|
data/lib/safemode/core_ext.rb
CHANGED
@@ -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 = {}
|
data/lib/safemode/exceptions.rb
CHANGED
@@ -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)
|
data/lib/safemode/parser.rb
CHANGED
@@ -26,19 +26,22 @@ module Safemode
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
def jail(str, parentheses = false)
|
30
|
-
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
|
-
|
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
|
-
|
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(
|
144
|
-
receiver_node_type =
|
145
|
-
receiver = process
|
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(
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
168
|
-
|
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
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
|
data/lib/safemode/scope.rb
CHANGED
@@ -1,58 +1,64 @@
|
|
1
1
|
module Safemode
|
2
2
|
class Scope < Blankslate
|
3
|
-
def initialize(delegate = nil, delegate_methods = [])
|
4
|
-
@delegate = delegate
|
3
|
+
def initialize(delegate = nil, delegate_methods = [], instance_vars: {}, locals: {}, &block)
|
4
|
+
@delegate = delegate
|
5
5
|
@delegate_methods = delegate_methods
|
6
|
-
@locals = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def bind(instance_vars = {}, locals = {}, &block)
|
10
6
|
@locals = symbolize_keys(locals) # why can't I just pull them to local scope in the same way like instance_vars?
|
11
7
|
instance_vars = symbolize_keys(instance_vars)
|
12
8
|
instance_vars.each {|key, obj| eval "@#{key} = instance_vars[:#{key}]" }
|
13
9
|
@_safemode_output = ''
|
14
|
-
binding
|
10
|
+
@binding = binding
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_binding
|
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
|
54
|
-
|
55
|
-
|
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,24 +35,25 @@ 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
|
+
@delegate_methods = delegate_methods
|
44
45
|
@filename = filename
|
45
46
|
@line = line
|
46
|
-
end
|
47
|
+
end
|
47
48
|
|
48
49
|
def eval(code, assigns = {}, locals = {}, &block)
|
49
50
|
code = Parser.jail(code)
|
50
|
-
|
51
|
-
|
51
|
+
@scope = Scope.new(@delegate, @delegate_methods, instance_vars: assigns, locals: locals, &block)
|
52
|
+
Kernel.eval(code, @scope.get_binding, @filename || __FILE__, @line || __LINE__)
|
52
53
|
end
|
53
|
-
|
54
|
+
|
54
55
|
def output
|
55
56
|
@scope.output
|
56
|
-
end
|
57
|
+
end
|
57
58
|
end
|
58
59
|
end
|
data/safemode.gemspec
CHANGED
@@ -1,19 +1,26 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: safemode 1.3.7 ruby lib
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
6
4
|
|
7
5
|
Gem::Specification.new do |s|
|
8
6
|
s.name = "safemode".freeze
|
9
|
-
s.version = "1.
|
7
|
+
s.version = "1.4.0"
|
8
|
+
s.date = Date.today
|
9
|
+
|
10
|
+
s.summary = "A library for safe evaluation of Ruby code based on ParseTree/RubyParser and Ruby2Ruby"
|
11
|
+
s.description = "A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby. Provides Rails ActionView template handlers for ERB and Haml."
|
12
|
+
s.homepage = "https://github.com/svenfuchs/safemode"
|
13
|
+
s.licenses = ["MIT"]
|
14
|
+
|
15
|
+
s.authors = [
|
16
|
+
"Sven Fuchs",
|
17
|
+
"Peter Cooper",
|
18
|
+
"Matthias Viehweger",
|
19
|
+
"Kingsley Hendrickse",
|
20
|
+
"Ohad Levy",
|
21
|
+
"Dmitri Dolguikh",
|
22
|
+
]
|
10
23
|
|
11
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
-
s.require_paths = ["lib".freeze]
|
13
|
-
s.authors = ["Sven Fuchs".freeze, "Peter Cooper".freeze, "Matthias Viehweger".freeze, "Kingsley Hendrickse".freeze, "Ohad Levy".freeze, "Dmitri Dolguikh".freeze]
|
14
|
-
s.date = "2022-04-26"
|
15
|
-
s.description = "A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby. Provides Rails ActionView template handlers for ERB and Haml.".freeze
|
16
|
-
s.email = "ohadlevy@gmail.com".freeze
|
17
24
|
s.extra_rdoc_files = [
|
18
25
|
"LICENSE",
|
19
26
|
"README.markdown"
|
@@ -23,15 +30,12 @@ Gem::Specification.new do |s|
|
|
23
30
|
"LICENSE",
|
24
31
|
"README.markdown",
|
25
32
|
"Rakefile",
|
26
|
-
"VERSION",
|
27
33
|
"demo.rb",
|
28
34
|
"init.rb",
|
29
35
|
"lib/action_view/template_handlers/safe_erb.rb",
|
30
36
|
"lib/action_view/template_handlers/safe_haml.rb",
|
31
37
|
"lib/action_view/template_handlers/safemode_handler.rb",
|
32
38
|
"lib/haml/safemode.rb",
|
33
|
-
"lib/ruby_parser_string_io_patch.diff",
|
34
|
-
"lib/rubyparser_bug.rb",
|
35
39
|
"lib/safemode.rb",
|
36
40
|
"lib/safemode/blankslate.rb",
|
37
41
|
"lib/safemode/core_ext.rb",
|
@@ -47,42 +51,15 @@ Gem::Specification.new do |s|
|
|
47
51
|
"test/test_safemode_eval.rb",
|
48
52
|
"test/test_safemode_parser.rb"
|
49
53
|
]
|
50
|
-
s.homepage = "https://github.com/svenfuchs/safemode".freeze
|
51
|
-
s.licenses = ["MIT".freeze]
|
52
|
-
s.rubygems_version = "2.7.6".freeze
|
53
|
-
s.summary = "A library for safe evaluation of Ruby code based on ParseTree/RubyParser and Ruby2Ruby".freeze
|
54
54
|
|
55
|
-
|
56
|
-
s.specification_version = 4
|
55
|
+
s.required_ruby_version = ">= 2.7", "< 3.1"
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
s.add_runtime_dependency(%q<sexp_processor>.freeze, [">= 4.10.0"])
|
62
|
-
s.add_development_dependency(%q<jeweler>.freeze, [">= 0"])
|
63
|
-
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
|
64
|
-
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
65
|
-
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
66
|
-
s.add_development_dependency(%q<test-unit>.freeze, [">= 0"])
|
67
|
-
else
|
68
|
-
s.add_dependency(%q<ruby2ruby>.freeze, [">= 2.4.0"])
|
69
|
-
s.add_dependency(%q<ruby_parser>.freeze, [">= 3.10.1"])
|
70
|
-
s.add_dependency(%q<sexp_processor>.freeze, [">= 4.10.0"])
|
71
|
-
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
72
|
-
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
73
|
-
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
74
|
-
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
75
|
-
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
76
|
-
end
|
77
|
-
else
|
78
|
-
s.add_dependency(%q<ruby2ruby>.freeze, [">= 2.4.0"])
|
79
|
-
s.add_dependency(%q<ruby_parser>.freeze, [">= 3.10.1"])
|
80
|
-
s.add_dependency(%q<sexp_processor>.freeze, [">= 4.10.0"])
|
81
|
-
s.add_dependency(%q<jeweler>.freeze, [">= 0"])
|
82
|
-
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
83
|
-
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
84
|
-
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
85
|
-
s.add_dependency(%q<test-unit>.freeze, [">= 0"])
|
86
|
-
end
|
87
|
-
end
|
57
|
+
s.add_runtime_dependency "ruby2ruby", ">= 2.4.0"
|
58
|
+
s.add_runtime_dependency "ruby_parser", ">= 3.10.1"
|
59
|
+
s.add_runtime_dependency "sexp_processor", ">= 4.10.0"
|
88
60
|
|
61
|
+
s.add_development_dependency "rake"
|
62
|
+
s.add_development_dependency "rdoc"
|
63
|
+
s.add_development_dependency "simplecov"
|
64
|
+
s.add_development_dependency "test-unit"
|
65
|
+
end
|
data/test/test_erb_eval.rb
CHANGED
@@ -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
|
114
|
-
|
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|
|
data/test/test_safemode_eval.rb
CHANGED
@@ -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')
|
@@ -80,6 +84,20 @@ class TestSafemodeEval < Test::Unit::TestCase
|
|
80
84
|
assert_raise_security '"#{`ls -a`}"'
|
81
85
|
end
|
82
86
|
|
87
|
+
def test_should_not_allow_access_to_bind
|
88
|
+
assert_raise_security "self.bind('an arg')"
|
89
|
+
end
|
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
|
+
|
83
101
|
TestHelper.no_method_error_raising_calls.each do |call|
|
84
102
|
call.gsub!('"', '\\\\"')
|
85
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.
|
4
|
+
version: 1.4.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:
|
16
|
+
date: 2023-12-11 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: ruby2ruby
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: 4.10.0
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
|
-
name:
|
61
|
+
name: rake
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
63
63
|
requirements:
|
64
64
|
- - ">="
|
@@ -72,7 +72,7 @@ dependencies:
|
|
72
72
|
- !ruby/object:Gem::Version
|
73
73
|
version: '0'
|
74
74
|
- !ruby/object:Gem::Dependency
|
75
|
-
name:
|
75
|
+
name: rdoc
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
77
77
|
requirements:
|
78
78
|
- - ">="
|
@@ -85,20 +85,6 @@ dependencies:
|
|
85
85
|
- - ">="
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '0'
|
88
|
-
- !ruby/object:Gem::Dependency
|
89
|
-
name: rdoc
|
90
|
-
requirement: !ruby/object:Gem::Requirement
|
91
|
-
requirements:
|
92
|
-
- - "~>"
|
93
|
-
- !ruby/object:Gem::Version
|
94
|
-
version: '3.12'
|
95
|
-
type: :development
|
96
|
-
prerelease: false
|
97
|
-
version_requirements: !ruby/object:Gem::Requirement
|
98
|
-
requirements:
|
99
|
-
- - "~>"
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
version: '3.12'
|
102
88
|
- !ruby/object:Gem::Dependency
|
103
89
|
name: simplecov
|
104
90
|
requirement: !ruby/object:Gem::Requirement
|
@@ -129,7 +115,7 @@ dependencies:
|
|
129
115
|
version: '0'
|
130
116
|
description: A library for safe evaluation of Ruby code based on RubyParser and Ruby2Ruby.
|
131
117
|
Provides Rails ActionView template handlers for ERB and Haml.
|
132
|
-
email:
|
118
|
+
email:
|
133
119
|
executables: []
|
134
120
|
extensions: []
|
135
121
|
extra_rdoc_files:
|
@@ -140,15 +126,12 @@ files:
|
|
140
126
|
- LICENSE
|
141
127
|
- README.markdown
|
142
128
|
- Rakefile
|
143
|
-
- VERSION
|
144
129
|
- demo.rb
|
145
130
|
- init.rb
|
146
131
|
- lib/action_view/template_handlers/safe_erb.rb
|
147
132
|
- lib/action_view/template_handlers/safe_haml.rb
|
148
133
|
- lib/action_view/template_handlers/safemode_handler.rb
|
149
134
|
- lib/haml/safemode.rb
|
150
|
-
- lib/ruby_parser_string_io_patch.diff
|
151
|
-
- lib/rubyparser_bug.rb
|
152
135
|
- lib/safemode.rb
|
153
136
|
- lib/safemode/blankslate.rb
|
154
137
|
- lib/safemode/core_ext.rb
|
@@ -167,7 +150,7 @@ homepage: https://github.com/svenfuchs/safemode
|
|
167
150
|
licenses:
|
168
151
|
- MIT
|
169
152
|
metadata: {}
|
170
|
-
post_install_message:
|
153
|
+
post_install_message:
|
171
154
|
rdoc_options: []
|
172
155
|
require_paths:
|
173
156
|
- lib
|
@@ -175,15 +158,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
158
|
requirements:
|
176
159
|
- - ">="
|
177
160
|
- !ruby/object:Gem::Version
|
178
|
-
version: '
|
161
|
+
version: '2.7'
|
162
|
+
- - "<"
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '3.1'
|
179
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
166
|
requirements:
|
181
167
|
- - ">="
|
182
168
|
- !ruby/object:Gem::Version
|
183
169
|
version: '0'
|
184
170
|
requirements: []
|
185
|
-
rubygems_version: 3.
|
186
|
-
signing_key:
|
171
|
+
rubygems_version: 3.1.6
|
172
|
+
signing_key:
|
187
173
|
specification_version: 4
|
188
174
|
summary: A library for safe evaluation of Ruby code based on ParseTree/RubyParser
|
189
175
|
and Ruby2Ruby
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
1.3.6
|
@@ -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
|