1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* ext/psych/lib/psych.rb: Adding Psych.safe_load for loading a user

defined, restricted subset of Ruby object types.
* ext/psych/lib/psych/class_loader.rb: A class loader for
  encapsulating the logic for which objects are allowed to be
  deserialized.
* ext/psych/lib/psych/deprecated.rb: Changes to use the class loader
* ext/psych/lib/psych/exception.rb: ditto
* ext/psych/lib/psych/json/stream.rb: ditto
* ext/psych/lib/psych/nodes/node.rb: ditto
* ext/psych/lib/psych/scalar_scanner.rb: ditto
* ext/psych/lib/psych/stream.rb: ditto
* ext/psych/lib/psych/streaming.rb: ditto
* ext/psych/lib/psych/visitors/json_tree.rb: ditto
* ext/psych/lib/psych/visitors/to_ruby.rb: ditto
* ext/psych/lib/psych/visitors/yaml_tree.rb: ditto
* ext/psych/psych_to_ruby.c: ditto
* test/psych/helper.rb: ditto
* test/psych/test_safe_load.rb: tests for restricted subset.
* test/psych/test_scalar_scanner.rb: ditto
* test/psych/visitors/test_to_ruby.rb: ditto
* test/psych/visitors/test_yaml_tree.rb: ditto

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@40750 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
tenderlove 2013-05-14 17:26:41 +00:00
parent d7f06a665c
commit 7ceafcbdf5
19 changed files with 382 additions and 59 deletions

View file

@ -1,3 +1,27 @@
Wed May 15 02:22:16 2013 Aaron Patterson <aaron@tenderlovemaking.com>
* ext/psych/lib/psych.rb: Adding Psych.safe_load for loading a user
defined, restricted subset of Ruby object types.
* ext/psych/lib/psych/class_loader.rb: A class loader for
encapsulating the logic for which objects are allowed to be
deserialized.
* ext/psych/lib/psych/deprecated.rb: Changes to use the class loader
* ext/psych/lib/psych/exception.rb: ditto
* ext/psych/lib/psych/json/stream.rb: ditto
* ext/psych/lib/psych/nodes/node.rb: ditto
* ext/psych/lib/psych/scalar_scanner.rb: ditto
* ext/psych/lib/psych/stream.rb: ditto
* ext/psych/lib/psych/streaming.rb: ditto
* ext/psych/lib/psych/visitors/json_tree.rb: ditto
* ext/psych/lib/psych/visitors/to_ruby.rb: ditto
* ext/psych/lib/psych/visitors/yaml_tree.rb: ditto
* ext/psych/psych_to_ruby.c: ditto
* test/psych/helper.rb: ditto
* test/psych/test_safe_load.rb: tests for restricted subset.
* test/psych/test_scalar_scanner.rb: ditto
* test/psych/visitors/test_to_ruby.rb: ditto
* test/psych/visitors/test_yaml_tree.rb: ditto
Wed May 15 02:06:35 2013 Aaron Patterson <aaron@tenderlovemaking.com> Wed May 15 02:06:35 2013 Aaron Patterson <aaron@tenderlovemaking.com>
* test/psych/helper.rb: envutil is not available outside Ruby, so * test/psych/helper.rb: envutil is not available outside Ruby, so

View file

@ -245,6 +245,55 @@ module Psych
result ? result.to_ruby : result result ? result.to_ruby : result
end end
###
# Safely load the yaml string in +yaml+. By default, only the following
# classes are allowed to be deserialized:
#
# * TrueClass
# * FalseClass
# * NilClass
# * Numeric
# * String
# * Array
# * Hash
#
# Recursive data structures are not allowed by default. Arbitrary classes
# can be allowed by adding those classes to the +whitelist+. They are
# additive. For example, to allow Date deserialization:
#
# Psych.safe_load(yaml, [Date])
#
# Now the Date class can be loaded in addition to the classes listed above.
#
# Aliases can be explicitly allowed by changing the +aliases+ parameter.
# For example:
#
# x = []
# x << x
# yaml = Psych.dump x
# Psych.safe_load yaml # => raises an exception
# Psych.safe_load yaml, [], [], true # => loads the aliases
#
# A Psych::DisallowedClass exception will be raised if the yaml contains a
# class that isn't in the whitelist.
#
# A Psych::BadAlias exception will be raised if the yaml contains aliases
# but the +aliases+ parameter is set to false.
def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases = false, filename = nil
result = parse(yaml, filename)
return unless result
class_loader = ClassLoader::Restricted.new(whitelist_classes.map(&:to_s),
whitelist_symbols.map(&:to_s))
scanner = ScalarScanner.new class_loader
if aliases
visitor = Visitors::ToRuby.new scanner, class_loader
else
visitor = Visitors::NoAliasRuby.new scanner, class_loader
end
visitor.accept result
end
### ###
# Parse a YAML string in +yaml+. Returns the Psych::Nodes::Document. # Parse a YAML string in +yaml+. Returns the Psych::Nodes::Document.
# +filename+ is used in the exception message if a Psych::SyntaxError is # +filename+ is used in the exception message if a Psych::SyntaxError is
@ -355,7 +404,7 @@ module Psych
io = nil io = nil
end end
visitor = Psych::Visitors::YAMLTree.new options visitor = Psych::Visitors::YAMLTree.create options
visitor << o visitor << o
visitor.tree.yaml io, options visitor.tree.yaml io, options
end end
@ -367,7 +416,7 @@ module Psych
# #
# Psych.dump_stream("foo\n ", {}) # => "--- ! \"foo\\n \"\n--- {}\n" # Psych.dump_stream("foo\n ", {}) # => "--- ! \"foo\\n \"\n--- {}\n"
def self.dump_stream *objects def self.dump_stream *objects
visitor = Psych::Visitors::YAMLTree.new {} visitor = Psych::Visitors::YAMLTree.create({})
objects.each do |o| objects.each do |o|
visitor << o visitor << o
end end
@ -377,7 +426,7 @@ module Psych
### ###
# Dump Ruby +object+ to a JSON string. # Dump Ruby +object+ to a JSON string.
def self.to_json object def self.to_json object
visitor = Psych::Visitors::JSONTree.new visitor = Psych::Visitors::JSONTree.create
visitor << object visitor << object
visitor.tree.yaml visitor.tree.yaml
end end
@ -435,7 +484,7 @@ module Psych
@load_tags = {} @load_tags = {}
@dump_tags = {} @dump_tags = {}
def self.add_tag tag, klass def self.add_tag tag, klass
@load_tags[tag] = klass @load_tags[tag] = klass.name
@dump_tags[klass] = tag @dump_tags[klass] = tag
end end

View file

@ -0,0 +1,101 @@
require 'psych/omap'
require 'psych/set'
module Psych
class ClassLoader # :nodoc:
BIG_DECIMAL = 'BigDecimal'
COMPLEX = 'Complex'
DATE = 'Date'
DATE_TIME = 'DateTime'
EXCEPTION = 'Exception'
OBJECT = 'Object'
PSYCH_OMAP = 'Psych::Omap'
PSYCH_SET = 'Psych::Set'
RANGE = 'Range'
RATIONAL = 'Rational'
REGEXP = 'Regexp'
STRUCT = 'Struct'
SYMBOL = 'Symbol'
def initialize
@cache = CACHE.dup
end
def load klassname
return nil if !klassname || klassname.empty?
find klassname
end
def symbolize sym
symbol
sym.to_sym
end
constants.each do |const|
konst = const_get const
define_method(const.to_s.downcase) do
load konst
end
end
private
def find klassname
@cache[klassname] ||= resolve(klassname)
end
def resolve klassname
name = klassname
retried = false
begin
path2class(name)
rescue ArgumentError, NameError => ex
unless retried
name = "Struct::#{name}"
retried = ex
retry
end
raise retried
end
end
CACHE = Hash[constants.map { |const|
val = const_get const
begin
[val, ::Object.const_get(val)]
rescue
nil
end
}.compact]
class Restricted < ClassLoader
def initialize classes, symbols
@classes = classes
@symbols = symbols
super()
end
def symbolize sym
return super if @symbols.empty?
if @symbols.include? sym
super
else
raise DisallowedClass, 'Symbol'
end
end
private
def find klassname
if @classes.include? klassname
super
else
raise DisallowedClass, klassname
end
end
end
end
end

View file

@ -35,7 +35,8 @@ module Psych
warn "#{caller[0]}: detect_implicit is deprecated" if $VERBOSE warn "#{caller[0]}: detect_implicit is deprecated" if $VERBOSE
return '' unless String === thing return '' unless String === thing
return 'null' if '' == thing return 'null' if '' == thing
ScalarScanner.new.tokenize(thing).class.name.downcase ss = ScalarScanner.new(ClassLoader.new)
ss.tokenize(thing).class.name.downcase
end end
def self.add_ruby_type type_tag, &block def self.add_ruby_type type_tag, &block

View file

@ -4,4 +4,10 @@ module Psych
class BadAlias < Exception class BadAlias < Exception
end end
class DisallowedClass < Exception
def initialize klass_name
super "Tried to load unspecified class: #{klass_name}"
end
end
end end

View file

@ -6,6 +6,7 @@ module Psych
class Stream < Psych::Visitors::JSONTree class Stream < Psych::Visitors::JSONTree
include Psych::JSON::RubyEvents include Psych::JSON::RubyEvents
include Psych::Streaming include Psych::Streaming
extend Psych::Streaming::ClassMethods
class Emitter < Psych::Stream::Emitter # :nodoc: class Emitter < Psych::Stream::Emitter # :nodoc:
include Psych::JSON::YAMLEvents include Psych::JSON::YAMLEvents

View file

@ -1,4 +1,6 @@
require 'stringio' require 'stringio'
require 'psych/class_loader'
require 'psych/scalar_scanner'
module Psych module Psych
module Nodes module Nodes
@ -32,7 +34,7 @@ module Psych
# #
# See also Psych::Visitors::ToRuby # See also Psych::Visitors::ToRuby
def to_ruby def to_ruby
Visitors::ToRuby.new.accept self Visitors::ToRuby.create.accept(self)
end end
alias :transform :to_ruby alias :transform :to_ruby

View file

@ -19,10 +19,13 @@ module Psych
|[-+]?(?:0|[1-9][0-9_]*) (?# base 10) |[-+]?(?:0|[1-9][0-9_]*) (?# base 10)
|[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x
attr_reader :class_loader
# Create a new scanner # Create a new scanner
def initialize def initialize class_loader
@string_cache = {} @string_cache = {}
@symbol_cache = {} @symbol_cache = {}
@class_loader = class_loader
end end
# Tokenize +string+ returning the ruby object # Tokenize +string+ returning the ruby object
@ -63,7 +66,7 @@ module Psych
when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/ when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
require 'date' require 'date'
begin begin
Date.strptime(string, '%Y-%m-%d') class_loader.date.strptime(string, '%Y-%m-%d')
rescue ArgumentError rescue ArgumentError
string string
end end
@ -75,9 +78,9 @@ module Psych
Float::NAN Float::NAN
when /^:./ when /^:./
if string =~ /^:(["'])(.*)\1/ if string =~ /^:(["'])(.*)\1/
@symbol_cache[string] = $2.sub(/^:/, '').to_sym @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, ''))
else else
@symbol_cache[string] = string.sub(/^:/, '').to_sym @symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, ''))
end end
when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/ when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
i = 0 i = 0
@ -117,6 +120,8 @@ module Psych
### ###
# Parse and return a Time from +string+ # Parse and return a Time from +string+
def parse_time string def parse_time string
klass = class_loader.load 'Time'
date, time = *(string.split(/[ tT]/, 2)) date, time = *(string.split(/[ tT]/, 2))
(yy, m, dd) = date.split('-').map { |x| x.to_i } (yy, m, dd) = date.split('-').map { |x| x.to_i }
md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/) md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/)
@ -124,10 +129,10 @@ module Psych
(hh, mm, ss) = md[1].split(':').map { |x| x.to_i } (hh, mm, ss) = md[1].split(':').map { |x| x.to_i }
us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000 us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000
time = Time.utc(yy, m, dd, hh, mm, ss, us) time = klass.utc(yy, m, dd, hh, mm, ss, us)
return time if 'Z' == md[3] return time if 'Z' == md[3]
return Time.at(time.to_i, us) unless md[3] return klass.at(time.to_i, us) unless md[3]
tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) } tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) }
offset = tz.first * 3600 offset = tz.first * 3600
@ -138,7 +143,7 @@ module Psych
offset += ((tz[1] || 0) * 60) offset += ((tz[1] || 0) * 60)
end end
Time.at((time - offset).to_i, us) klass.at((time - offset).to_i, us)
end end
end end
end end

View file

@ -32,5 +32,6 @@ module Psych
end end
include Psych::Streaming include Psych::Streaming
extend Psych::Streaming::ClassMethods
end end
end end

View file

@ -1,10 +1,15 @@
module Psych module Psych
module Streaming module Streaming
module ClassMethods
### ###
# Create a new streaming emitter. Emitter will print to +io+. See # Create a new streaming emitter. Emitter will print to +io+. See
# Psych::Stream for an example. # Psych::Stream for an example.
def initialize io def new io
super({}, self.class.const_get(:Emitter).new(io)) emitter = const_get(:Emitter).new(io)
class_loader = ClassLoader.new
ss = ScalarScanner.new class_loader
super(emitter, ss, {})
end
end end
### ###

View file

@ -5,8 +5,11 @@ module Psych
class JSONTree < YAMLTree class JSONTree < YAMLTree
include Psych::JSON::RubyEvents include Psych::JSON::RubyEvents
def initialize options = {}, emitter = Psych::JSON::TreeBuilder.new def self.create options = {}
super emitter = Psych::JSON::TreeBuilder.new
class_loader = ClassLoader.new
ss = ScalarScanner.new class_loader
new(emitter, ss, options)
end end
def accept target def accept target

View file

@ -1,4 +1,5 @@
require 'psych/scalar_scanner' require 'psych/scalar_scanner'
require 'psych/class_loader'
require 'psych/exception' require 'psych/exception'
unless defined?(Regexp::NOENCODING) unless defined?(Regexp::NOENCODING)
@ -10,11 +11,20 @@ module Psych
### ###
# This class walks a YAML AST, converting each node to ruby # This class walks a YAML AST, converting each node to ruby
class ToRuby < Psych::Visitors::Visitor class ToRuby < Psych::Visitors::Visitor
def initialize ss = ScalarScanner.new def self.create
class_loader = ClassLoader.new
scanner = ScalarScanner.new class_loader
new(scanner, class_loader)
end
attr_reader :class_loader
def initialize ss, class_loader
super() super()
@st = {} @st = {}
@ss = ss @ss = ss
@domain_types = Psych.domain_types @domain_types = Psych.domain_types
@class_loader = class_loader
end end
def accept target def accept target
@ -33,7 +43,7 @@ module Psych
end end
def deserialize o def deserialize o
if klass = Psych.load_tags[o.tag] if klass = resolve_class(Psych.load_tags[o.tag])
instance = klass.allocate instance = klass.allocate
if instance.respond_to?(:init_with) if instance.respond_to?(:init_with)
@ -60,19 +70,23 @@ module Psych
end end
when '!ruby/object:BigDecimal' when '!ruby/object:BigDecimal'
require 'bigdecimal' require 'bigdecimal'
BigDecimal._load o.value class_loader.big_decimal._load o.value
when "!ruby/object:DateTime" when "!ruby/object:DateTime"
class_loader.date_time
require 'date' require 'date'
@ss.parse_time(o.value).to_datetime @ss.parse_time(o.value).to_datetime
when "!ruby/object:Complex" when "!ruby/object:Complex"
class_loader.complex
Complex(o.value) Complex(o.value)
when "!ruby/object:Rational" when "!ruby/object:Rational"
class_loader.rational
Rational(o.value) Rational(o.value)
when "!ruby/class", "!ruby/module" when "!ruby/class", "!ruby/module"
resolve_class o.value resolve_class o.value
when "tag:yaml.org,2002:float", "!float" when "tag:yaml.org,2002:float", "!float"
Float(@ss.tokenize(o.value)) Float(@ss.tokenize(o.value))
when "!ruby/regexp" when "!ruby/regexp"
klass = class_loader.regexp
o.value =~ /^\/(.*)\/([mixn]*)$/ o.value =~ /^\/(.*)\/([mixn]*)$/
source = $1 source = $1
options = 0 options = 0
@ -86,15 +100,16 @@ module Psych
else lang = option else lang = option
end end
end end
Regexp.new(*[source, options, lang].compact) klass.new(*[source, options, lang].compact)
when "!ruby/range" when "!ruby/range"
klass = class_loader.range
args = o.value.split(/([.]{2,3})/, 2).map { |s| args = o.value.split(/([.]{2,3})/, 2).map { |s|
accept Nodes::Scalar.new(s) accept Nodes::Scalar.new(s)
} }
args.push(args.delete_at(1) == '...') args.push(args.delete_at(1) == '...')
Range.new(*args) klass.new(*args)
when /^!ruby\/sym(bol)?:?(.*)?$/ when /^!ruby\/sym(bol)?:?(.*)?$/
o.value.to_sym class_loader.symbolize o.value
else else
@ss.tokenize o.value @ss.tokenize o.value
end end
@ -106,7 +121,7 @@ module Psych
end end
def visit_Psych_Nodes_Sequence o def visit_Psych_Nodes_Sequence o
if klass = Psych.load_tags[o.tag] if klass = resolve_class(Psych.load_tags[o.tag])
instance = klass.allocate instance = klass.allocate
if instance.respond_to?(:init_with) if instance.respond_to?(:init_with)
@ -138,22 +153,24 @@ module Psych
end end
def visit_Psych_Nodes_Mapping o def visit_Psych_Nodes_Mapping o
return revive(Psych.load_tags[o.tag], o) if Psych.load_tags[o.tag] if Psych.load_tags[o.tag]
return revive(resolve_class(Psych.load_tags[o.tag]), o)
end
return revive_hash({}, o) unless o.tag return revive_hash({}, o) unless o.tag
case o.tag case o.tag
when /^!ruby\/struct:?(.*)?$/ when /^!ruby\/struct:?(.*)?$/
klass = resolve_class($1) klass = resolve_class($1) if $1
if klass if klass
s = register(o, klass.allocate) s = register(o, klass.allocate)
members = {} members = {}
struct_members = s.members.map { |x| x.to_sym } struct_members = s.members.map { |x| class_loader.symbolize x }
o.children.each_slice(2) do |k,v| o.children.each_slice(2) do |k,v|
member = accept(k) member = accept(k)
value = accept(v) value = accept(v)
if struct_members.include?(member.to_sym) if struct_members.include?(class_loader.symbolize(member))
s.send("#{member}=", value) s.send("#{member}=", value)
else else
members[member.to_s.sub(/^@/, '')] = value members[member.to_s.sub(/^@/, '')] = value
@ -161,22 +178,27 @@ module Psych
end end
init_with(s, members, o) init_with(s, members, o)
else else
klass = class_loader.struct
members = o.children.map { |c| accept c } members = o.children.map { |c| accept c }
h = Hash[*members] h = Hash[*members]
Struct.new(*h.map { |k,v| k.to_sym }).new(*h.map { |k,v| v }) klass.new(*h.map { |k,v|
class_loader.symbolize k
}).new(*h.map { |k,v| v })
end end
when /^!ruby\/object:?(.*)?$/ when /^!ruby\/object:?(.*)?$/
name = $1 || 'Object' name = $1 || 'Object'
if name == 'Complex' if name == 'Complex'
class_loader.complex
h = Hash[*o.children.map { |c| accept c }] h = Hash[*o.children.map { |c| accept c }]
register o, Complex(h['real'], h['image']) register o, Complex(h['real'], h['image'])
elsif name == 'Rational' elsif name == 'Rational'
class_loader.rational
h = Hash[*o.children.map { |c| accept c }] h = Hash[*o.children.map { |c| accept c }]
register o, Rational(h['numerator'], h['denominator']) register o, Rational(h['numerator'], h['denominator'])
else else
obj = revive((resolve_class(name) || Object), o) obj = revive((resolve_class(name) || class_loader.object), o)
obj obj
end end
@ -214,18 +236,19 @@ module Psych
list list
when '!ruby/range' when '!ruby/range'
klass = class_loader.range
h = Hash[*o.children.map { |c| accept c }] h = Hash[*o.children.map { |c| accept c }]
register o, Range.new(h['begin'], h['end'], h['excl']) register o, klass.new(h['begin'], h['end'], h['excl'])
when /^!ruby\/exception:?(.*)?$/ when /^!ruby\/exception:?(.*)?$/
h = Hash[*o.children.map { |c| accept c }] h = Hash[*o.children.map { |c| accept c }]
e = build_exception((resolve_class($1) || Exception), e = build_exception((resolve_class($1) || class_loader.exception),
h.delete('message')) h.delete('message'))
init_with(e, h, o) init_with(e, h, o)
when '!set', 'tag:yaml.org,2002:set' when '!set', 'tag:yaml.org,2002:set'
set = Psych::Set.new set = class_loader.psych_set.new
@st[o.anchor] = set if o.anchor @st[o.anchor] = set if o.anchor
o.children.each_slice(2) do |k,v| o.children.each_slice(2) do |k,v|
set[accept(k)] = accept(v) set[accept(k)] = accept(v)
@ -236,7 +259,7 @@ module Psych
revive_hash resolve_class($1).new, o revive_hash resolve_class($1).new, o
when '!omap', 'tag:yaml.org,2002:omap' when '!omap', 'tag:yaml.org,2002:omap'
map = register(o, Psych::Omap.new) map = register(o, class_loader.psych_omap.new)
o.children.each_slice(2) do |l,r| o.children.each_slice(2) do |l,r|
map[accept(l)] = accept r map[accept(l)] = accept r
end end
@ -336,21 +359,13 @@ module Psych
# Convert +klassname+ to a Class # Convert +klassname+ to a Class
def resolve_class klassname def resolve_class klassname
return nil unless klassname and not klassname.empty? class_loader.load klassname
end
end
name = klassname class NoAliasRuby < ToRuby
retried = false def visit_Psych_Nodes_Alias o
raise BadAlias, "Unknown alias: #{o.anchor}"
begin
path2class(name)
rescue ArgumentError, NameError => ex
unless retried
name = "Struct::#{name}"
retried = ex
retry
end
raise retried
end
end end
end end
end end

View file

@ -1,3 +1,7 @@
require 'psych/tree_builder'
require 'psych/scalar_scanner'
require 'psych/class_loader'
module Psych module Psych
module Visitors module Visitors
### ###
@ -36,7 +40,14 @@ module Psych
alias :finished? :finished alias :finished? :finished
alias :started? :started alias :started? :started
def initialize options = {}, emitter = TreeBuilder.new, ss = ScalarScanner.new def self.create options = {}, emitter = nil
emitter ||= TreeBuilder.new
class_loader = ClassLoader.new
ss = ScalarScanner.new class_loader
new(emitter, ss, options)
end
def initialize emitter, ss, options
super() super()
@started = false @started = false
@finished = false @finished = false

View file

@ -31,11 +31,13 @@ static VALUE path2class(VALUE self, VALUE path)
void Init_psych_to_ruby(void) void Init_psych_to_ruby(void)
{ {
VALUE psych = rb_define_module("Psych"); VALUE psych = rb_define_module("Psych");
VALUE class_loader = rb_define_class_under(psych, "ClassLoader", rb_cObject);
VALUE visitors = rb_define_module_under(psych, "Visitors"); VALUE visitors = rb_define_module_under(psych, "Visitors");
VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject);
cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor);
rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2);
rb_define_private_method(cPsychVisitorsToRuby, "path2class", path2class, 1); rb_define_private_method(class_loader, "path2class", path2class, 1);
} }
/* vim: set noet sws=4 sw=4: */ /* vim: set noet sws=4 sw=4: */

View file

@ -60,7 +60,7 @@ module Psych
end end
def assert_cycle( obj ) def assert_cycle( obj )
v = Visitors::YAMLTree.new v = Visitors::YAMLTree.create
v << obj v << obj
assert_equal(obj, Psych.load(v.tree.yaml)) assert_equal(obj, Psych.load(v.tree.yaml))
assert_equal( obj, Psych::load(Psych.dump(obj))) assert_equal( obj, Psych::load(Psych.dump(obj)))

View file

@ -0,0 +1,97 @@
require 'psych/helper'
module Psych
class TestSafeLoad < TestCase
class Foo; end
[1, 2.2, {}, [], "foo"].each do |obj|
define_method(:"test_basic_#{obj.class}") do
assert_safe_cycle obj
end
end
def test_no_recursion
x = []
x << x
assert_raises(Psych::BadAlias) do
Psych.safe_load Psych.dump(x)
end
end
def test_explicit_recursion
x = []
x << x
assert_equal(x, Psych.safe_load(Psych.dump(x), [], [], true))
end
def test_symbol_whitelist
yml = Psych.dump :foo
assert_raises(Psych::DisallowedClass) do
Psych.safe_load yml
end
assert_equal(:foo, Psych.safe_load(yml, [Symbol], [:foo]))
end
def test_symbol
assert_raises(Psych::DisallowedClass) do
assert_safe_cycle :foo
end
assert_raises(Psych::DisallowedClass) do
Psych.safe_load '--- !ruby/symbol foo', []
end
assert_safe_cycle :foo, [Symbol]
assert_safe_cycle :foo, %w{ Symbol }
assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', [Symbol])
end
def test_foo
assert_raises(Psych::DisallowedClass) do
Psych.safe_load '--- !ruby/object:Foo {}', [Foo]
end
assert_raises(Psych::DisallowedClass) do
assert_safe_cycle Foo.new
end
assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), [Foo]))
end
X = Struct.new(:x)
def test_struct_depends_on_sym
assert_safe_cycle(X.new, [X, Symbol])
assert_raises(Psych::DisallowedClass) do
cycle X.new, [X]
end
end
def test_anon_struct
assert Psych.safe_load(<<-eoyml, [Struct, Symbol])
--- !ruby/struct
foo: bar
eoyml
assert_raises(Psych::DisallowedClass) do
Psych.safe_load(<<-eoyml, [Struct])
--- !ruby/struct
foo: bar
eoyml
end
assert_raises(Psych::DisallowedClass) do
Psych.safe_load(<<-eoyml, [Symbol])
--- !ruby/struct
foo: bar
eoyml
end
end
private
def cycle object, whitelist = []
Psych.safe_load(Psych.dump(object), whitelist)
end
def assert_safe_cycle object, whitelist = []
other = cycle object, whitelist
assert_equal object, other
end
end
end

View file

@ -7,7 +7,7 @@ module Psych
def setup def setup
super super
@ss = Psych::ScalarScanner.new @ss = Psych::ScalarScanner.new ClassLoader.new
end end
def test_scan_time def test_scan_time

View file

@ -6,7 +6,7 @@ module Psych
class TestToRuby < TestCase class TestToRuby < TestCase
def setup def setup
super super
@visitor = ToRuby.new @visitor = ToRuby.create
end end
def test_object def test_object
@ -88,7 +88,7 @@ description:
end end
def test_exception def test_exception
exc = Exception.new 'hello' exc = ::Exception.new 'hello'
mapping = Nodes::Mapping.new nil, '!ruby/exception' mapping = Nodes::Mapping.new nil, '!ruby/exception'
mapping.children << Nodes::Scalar.new('message') mapping.children << Nodes::Scalar.new('message')

View file

@ -5,7 +5,7 @@ module Psych
class TestYAMLTree < TestCase class TestYAMLTree < TestCase
def setup def setup
super super
@v = Visitors::YAMLTree.new @v = Visitors::YAMLTree.create
end end
def test_tree_can_be_called_twice def test_tree_can_be_called_twice
@ -18,7 +18,7 @@ module Psych
def test_yaml_tree_can_take_an_emitter def test_yaml_tree_can_take_an_emitter
io = StringIO.new io = StringIO.new
e = Psych::Emitter.new io e = Psych::Emitter.new io
v = Visitors::YAMLTree.new({}, e) v = Visitors::YAMLTree.create({}, e)
v.start v.start
v << "hello world" v << "hello world"
v.finish v.finish