-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
08c3367
commit 50ebf6e
Showing
5 changed files
with
145 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
require "ruby_parser" | ||
|
||
# Whiltelist based hash string parser | ||
class SafeParser | ||
VERSION = "0.0.2" | ||
|
||
UnsafeError = Class.new(StandardError) | ||
attr_reader :string | ||
|
||
def initialize(string) | ||
@string = string | ||
end | ||
|
||
def safe_load | ||
parse(root_expression) | ||
end | ||
|
||
private | ||
|
||
def parse(expression) | ||
case expression.head | ||
when :hash | ||
Hash[*parse_into_array(expression.values)] | ||
when :array | ||
parse_into_array(expression.values) | ||
when :true | ||
true | ||
when :false | ||
false | ||
when :nil | ||
nil | ||
when :lit, :str | ||
expression.value | ||
else | ||
raise UnsafeError, "#{ string } is a bad hash" | ||
end | ||
end | ||
|
||
def root_expression | ||
@root_expression ||= RubyParser.new.parse(string) | ||
end | ||
|
||
def parse_into_array(expression) | ||
expression.map { |child_expression| parse(child_expression) } | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
require "minitest/autorun" | ||
require "minitest/spec" | ||
require "minitest/pride" | ||
require "safe_parser" | ||
|
||
describe SafeParser do | ||
describe "#safe_load" do | ||
it "fails if it is not a hash" do | ||
strs = ["Book.delete_all", "puts 'hello'", '"#{puts 123}"', "system('ls /')"] | ||
strs.each do |bad_str| | ||
assert_raises SafeParser::UnsafeError, "#{ bad_str } should not be safe" do | ||
SafeParser.new(bad_str).safe_load | ||
end | ||
end | ||
end | ||
|
||
it "fails if there are more than one expression" do | ||
strs = ["{ :a => 1 }; 'hello'", "{ :a => 1 }\n{ :b => 2 }"] | ||
strs.each do |bad_str| | ||
assert_raises SafeParser::UnsafeError, "#{ bad_str } should not be safe" do | ||
SafeParser.new(bad_str).safe_load | ||
end | ||
end | ||
end | ||
|
||
it "Does not define or redefine any methods" do | ||
str = '{ :a => refine(UnsafeError) { def safe?; "HAHAHA"; end } }' | ||
assert_raises SafeParser::UnsafeError, "#{ str } should not be safe" do | ||
SafeParser.new(str).safe_load | ||
end | ||
|
||
str = '{ :a => def SafeParser.safe?; "HAHAHA"; end }' | ||
assert_raises SafeParser::UnsafeError, "#{ str } should not be safe" do | ||
SafeParser.new(str).safe_load | ||
end | ||
end | ||
|
||
it "fails if it has assignment" do | ||
strs = ['{ :a => (SafeParser::TEST_CONSTANT = 1) }', | ||
'{ :a => (hello_world = 1) }'] | ||
strs.each do |str| | ||
assert_raises SafeParser::UnsafeError, "#{ str } should not be safe" do | ||
SafeParser.new(str).safe_load | ||
end | ||
end | ||
end | ||
|
||
it "fails if a hash has a method call" do | ||
strs = ["{ :a => 2 * 2 }", | ||
"{ :a => SOME_CONST }", | ||
"{ :a => system('ls /') }", | ||
"{ :a => Book.delete_all }", | ||
'{ :a => "#{500}" }', | ||
'{ :a => "#{ Book.delete_all }" }', | ||
'{ :a => refine(UnsafeError) { def safe?; "HAHAHA"; end } }', | ||
] | ||
strs.each do |bad_str| | ||
assert_raises SafeParser::UnsafeError, "#{ bad_str } should not be safe" do | ||
SafeParser.new(bad_str).safe_load | ||
end | ||
end | ||
end | ||
|
||
it "passes for hashes" do | ||
strs = ["{}", '{ "a" => "A" }', '{ :a => 123 }', '{ :a => true, :b => false, :c => true, "d" => nil }'] | ||
parsed = [{}, { "a" => "A" }, { a: 123 }, { a: true, b: false, c: true, "d" => nil }] | ||
strs.each.with_index do |good_str, i| | ||
assert_equal parsed[i], SafeParser.new(good_str).safe_load, "#{ good_str } should be safe" | ||
end | ||
end | ||
|
||
it "passes for hashes with sub hashes" do | ||
str = '{ :a => [1, 2, { "x" => "y" }] }' | ||
parsed = { a: [1, 2, { "x" => "y" }] } | ||
assert_equal parsed, SafeParser.new(str).safe_load | ||
end | ||
|
||
it "passes for simple literals" do | ||
strs = ["1", "'a string'", ":a_symbol", "false", "true", "12.34"] | ||
parsed = [1, "a string", :a_symbol, false, true, 12.34] | ||
strs.each.with_index do |good_str, i| | ||
assert_equal parsed[i], SafeParser.new(good_str).safe_load, "#{ good_str } should be safe" | ||
end | ||
|
||
assert_nil SafeParser.new("nil").safe_load, "The string 'nil' should be safe" | ||
end | ||
|
||
it "passes for a complex array" do | ||
strs = ["[]", "['a_string', :a_symbol, true, false, nil, 1_234_567, 12.34]", "[[123], { a: 1, \"b\" => 2}]"] | ||
parsed = [[], ['a_string', :a_symbol, true, false, nil, 1_234_567, 12.34], [[123], { a: 1, "b" => 2}]] | ||
strs.each.with_index do |good_str, i| | ||
assert_equal parsed[i], SafeParser.new(good_str).safe_load, "#{ good_str } should be safe" | ||
end | ||
end | ||
end | ||
end | ||
|