1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
require "set"
require "yaml"
# This needs to be defined up front in case any internal classes need to base
# their behavior off of this.
module SafeYAML
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : (defined?(Psych) && YAML == Psych ? "psych" : "syck")
end
require "safe_yaml/libyaml_checker"
require "safe_yaml/deep"
require "safe_yaml/parse/hexadecimal"
require "safe_yaml/parse/sexagesimal"
require "safe_yaml/parse/date"
require "safe_yaml/transform/transformation_map"
require "safe_yaml/transform/to_boolean"
require "safe_yaml/transform/to_date"
require "safe_yaml/transform/to_float"
require "safe_yaml/transform/to_integer"
require "safe_yaml/transform/to_nil"
require "safe_yaml/transform/to_symbol"
require "safe_yaml/transform"
require "safe_yaml/resolver"
require "safe_yaml/syck_hack" if SafeYAML::YAML_ENGINE == "syck" && defined?(JRUBY_VERSION)
module SafeYAML
MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
DEFAULT_OPTIONS = Deep.freeze({
:default_mode => nil,
:suppress_warnings => false,
:deserialize_symbols => false,
:whitelisted_tags => [],
:custom_initializers => {},
:raise_on_unknown_tag => false
})
OPTIONS = Deep.copy(DEFAULT_OPTIONS)
PREDEFINED_TAGS = {}
if YAML_ENGINE == "syck"
YAML.tagged_classes.each do |tag, klass|
PREDEFINED_TAGS[klass] = tag
end
else
# Special tags appear to be hard-coded in Psych:
# https://github.com/tenderlove/psych/blob/v1.3.4/lib/psych/visitors/to_ruby.rb
# Fortunately, there aren't many that SafeYAML doesn't already support.
PREDEFINED_TAGS.merge!({
Exception => "!ruby/exception",
Range => "!ruby/range",
Regexp => "!ruby/regexp",
})
end
Deep.freeze(PREDEFINED_TAGS)
module_function
def restore_defaults!
OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS))
end
def tag_safety_check!(tag, options)
return if tag.nil? || tag == "!"
if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag)
raise "Unknown YAML tag '#{tag}'"
end
end
def whitelist!(*classes)
classes.each do |klass|
whitelist_class!(klass)
end
end
def whitelist_class!(klass)
raise "#{klass} not a Class" unless klass.is_a?(::Class)
klass_name = klass.name
raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty?
# Whitelist any built-in YAML tags supplied by Syck or Psych.
predefined_tag = PREDEFINED_TAGS[klass]
if predefined_tag
OPTIONS[:whitelisted_tags] << predefined_tag
return
end
# Exception is exceptional (har har).
tag_class = klass < Exception ? "exception" : "object"
tag_prefix = case YAML_ENGINE
when "psych" then "!ruby/#{tag_class}"
when "syck" then "tag:ruby.yaml.org,2002:#{tag_class}"
else raise "unknown YAML_ENGINE #{YAML_ENGINE}"
end
OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}"
end
if YAML_ENGINE == "psych"
def tag_is_explicitly_trusted?(tag)
false
end
else
TRUSTED_TAGS = Set.new([
"tag:yaml.org,2002:binary",
"tag:yaml.org,2002:bool#no",
"tag:yaml.org,2002:bool#yes",
"tag:yaml.org,2002:float",
"tag:yaml.org,2002:float#fix",
"tag:yaml.org,2002:int",
"tag:yaml.org,2002:map",
"tag:yaml.org,2002:null",
"tag:yaml.org,2002:seq",
"tag:yaml.org,2002:str",
"tag:yaml.org,2002:timestamp",
"tag:yaml.org,2002:timestamp#ymd"
]).freeze
def tag_is_explicitly_trusted?(tag)
TRUSTED_TAGS.include?(tag)
end
end
if SafeYAML::YAML_ENGINE == "psych"
require "safe_yaml/psych_handler"
require "safe_yaml/psych_resolver"
require "safe_yaml/safe_to_ruby_visitor"
def self.load(yaml, filename=nil, options={})
# If the user hasn't whitelisted any tags, we can go with this implementation which is
# significantly faster.
if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty?
safe_handler = SafeYAML::PsychHandler.new(options) do |result|
return result
end
arguments_for_parse = [yaml]
arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
Psych::Parser.new(safe_handler).parse(*arguments_for_parse)
return safe_handler.result
else
safe_resolver = SafeYAML::PsychResolver.new(options)
tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ?
Psych.parse(yaml, filename) :
Psych.parse(yaml)
return safe_resolver.resolve_node(tree)
end
end
def self.load_file(filename, options={})
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
File.open(filename, 'r:bom|utf-8') { |f| self.load(f, filename, options) }
else
# Ruby pukes on 1.9.2 if we try to open an empty file w/ 'r:bom|utf-8';
# so we'll not specify those flags here. This mirrors the behavior for
# unsafe_load_file so it's probably preferable anyway.
self.load File.open(filename), nil, options
end
end
else
require "safe_yaml/syck_resolver"
require "safe_yaml/syck_node_monkeypatch"
def self.load(yaml, options={})
resolver = SafeYAML::SyckResolver.new(SafeYAML::OPTIONS.merge(options || {}))
tree = YAML.parse(yaml)
return resolver.resolve_node(tree)
end
def self.load_file(filename, options={})
File.open(filename) { |f| self.load(f, options) }
end
end
end
|