safe_yaml 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -1
- data/README.md +55 -21
- data/lib/safe_yaml.rb +49 -30
- data/lib/safe_yaml/psych_resolver.rb +52 -0
- data/lib/safe_yaml/psych_visitor.rb +13 -0
- data/lib/safe_yaml/resolver.rb +82 -0
- data/lib/safe_yaml/syck_node_monkeypatch.rb +17 -0
- data/lib/safe_yaml/syck_resolver.rb +22 -36
- data/lib/safe_yaml/transform.rb +5 -7
- data/lib/safe_yaml/transform/to_symbol.rb +1 -1
- data/lib/safe_yaml/version.rb +1 -1
- data/spec/psych_resolver_spec.rb +10 -0
- data/spec/{shared_specs.rb → resolver_specs.rb} +13 -5
- data/spec/safe_yaml_spec.rb +138 -14
- data/spec/spec_helper.rb +3 -1
- data/spec/syck_resolver_spec.rb +1 -8
- data/spec/transform/to_symbol_spec.rb +14 -14
- metadata +48 -27
- data/lib/safe_yaml/psych_handler.rb +0 -91
- data/spec/psych_handler_spec.rb +0 -17
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@ SafeYAML
|
|
3
3
|
|
4
4
|
[![Build Status](https://travis-ci.org/dtao/safe_yaml.png)](http://travis-ci.org/dtao/safe_yaml)
|
5
5
|
|
6
|
-
The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the
|
6
|
+
The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the ones recently](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/) [discovered in Rails](http://www.h-online.com/open/news/item/Rails-developers-close-another-extremely-critical-flaw-1793511.html)).
|
7
7
|
|
8
8
|
Installation
|
9
9
|
------------
|
@@ -47,11 +47,12 @@ Now, if you were to use `YAML.load` on user input anywhere in your application w
|
|
47
47
|
|
48
48
|
Observe:
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
```ruby
|
51
|
+
yaml = <<-EOYAML
|
52
|
+
--- !ruby/hash:ExploitableClassBuilder
|
53
|
+
"foo; end; puts %(I'm in yr system!); def bar": "baz"
|
54
|
+
EOYAML
|
55
|
+
```
|
55
56
|
|
56
57
|
> YAML.load(yaml)
|
57
58
|
I'm in yr system!
|
@@ -61,7 +62,7 @@ With SafeYAML, that attacker would be thwarted:
|
|
61
62
|
|
62
63
|
> require "safe_yaml"
|
63
64
|
=> true
|
64
|
-
> YAML.
|
65
|
+
> YAML.safe_load(yaml)
|
65
66
|
=> {"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}
|
66
67
|
|
67
68
|
Usage
|
@@ -73,20 +74,22 @@ Usage
|
|
73
74
|
|
74
75
|
By default, when you require the safe_yaml gem in your project, `YAML.load` is patched to internally call `safe_load`. The patched method also accepts a `:safe` flag to specify which version to use:
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
```ruby
|
78
|
+
# Ruby >= 1.9.3
|
79
|
+
YAML.load(yaml, filename, :safe => true) # calls safe_load
|
80
|
+
YAML.load(yaml, filename, :safe => false) # calls unsafe_load
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
-
|
82
|
+
# Ruby < 1.9.3
|
83
|
+
YAML.load(yaml, :safe => true) # calls safe_load
|
84
|
+
YAML.load(yaml, :safe => false) # calls unsafe_load
|
85
|
+
```
|
83
86
|
|
84
|
-
The default behavior can be switched to unsafe loading by calling `
|
87
|
+
The default behavior can be switched to unsafe loading by calling `SafeYAML::OPTIONS[:default_mode] = :unsafe`. In this case, the `:safe` flag still has the same effect, but the defaults are reversed (so calling `YAML.load` will have the same behavior as if the safe_yaml gem weren't required).
|
85
88
|
|
86
|
-
This gem will also warn you whenever you use `YAML.load` without specifying the `:safe` option
|
89
|
+
This gem will also warn you whenever you use `YAML.load` without specifying the `:safe` option, or if you have not explicitly specified a default mode using the `:default_mode` option.
|
87
90
|
|
88
|
-
|
89
|
-
|
91
|
+
Supported Types
|
92
|
+
---------------
|
90
93
|
|
91
94
|
The way that SafeYAML works is by restricting the kinds of objects that can be deserialized via `YAML.load`. More specifically, only the following types of objects can be deserialized by default:
|
92
95
|
|
@@ -99,15 +102,46 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
|
|
99
102
|
- Booleans
|
100
103
|
- Nils
|
101
104
|
|
102
|
-
|
105
|
+
Deserialization of symbols can also be enabled by setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` (for example, in an initializer). Be aware, however, that symbols in Ruby are not garbage-collected; therefore enabling symbol deserialization in your application may leave you vulnerable to [DOS attacks](http://en.wikipedia.org/wiki/Denial-of-service_attack).
|
106
|
+
|
107
|
+
Whitelisting Trusted Types
|
108
|
+
--------------------------
|
109
|
+
|
110
|
+
SafeYAML now also supports **whitelisting** certain YAML tags for trusted types. This is handy when your application may use YAML to serialize and deserialize certain types not listed above, which you know to be free of any deserialization-related vulnerabilities. You can whitelist tags via the `:whitelisted_tags` option:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# Using Syck (unfortunately, Syck and Psych use different tagging schemes)
|
114
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
|
115
|
+
|
116
|
+
# Using Psych
|
117
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
|
118
|
+
```
|
119
|
+
|
120
|
+
When SafeYAML encounters whitelisted tags in a YAML document, it will default to the deserialization capabilities of the underlying YAML engine (i.e., either Syck or Psych).
|
121
|
+
|
122
|
+
**However**, this feature will *not* allow would-be attackers to embed untrusted types within trusted types:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
yaml = <<-EOYAML
|
126
|
+
--- !ruby/object:OpenStruct
|
127
|
+
table:
|
128
|
+
:backdoor: !ruby/hash:ExploitableClassBuilder
|
129
|
+
"foo; end; puts %(I'm in yr system!); def bar": "baz"
|
130
|
+
EOYAML
|
131
|
+
```
|
132
|
+
|
133
|
+
> YAML.safe_load(yaml)
|
134
|
+
=> #<OpenStruct :backdoor={"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}>
|
135
|
+
|
136
|
+
Pretty sweet, right?
|
103
137
|
|
104
138
|
Known Issues
|
105
139
|
------------
|
106
140
|
|
107
|
-
|
141
|
+
Be aware that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
|
108
142
|
|
109
|
-
- [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so
|
110
|
-
- [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so
|
143
|
+
- [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` is necessary to allow Guard to work.
|
144
|
+
- [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` should allow it to work.
|
111
145
|
|
112
146
|
The above list will grow over time, as more issues are discovered.
|
113
147
|
|
data/lib/safe_yaml.rb
CHANGED
@@ -7,17 +7,25 @@ require "safe_yaml/transform/to_nil"
|
|
7
7
|
require "safe_yaml/transform/to_symbol"
|
8
8
|
require "safe_yaml/transform/to_time"
|
9
9
|
require "safe_yaml/transform"
|
10
|
-
require "safe_yaml/
|
10
|
+
require "safe_yaml/resolver"
|
11
11
|
|
12
12
|
module SafeYAML
|
13
13
|
MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
|
14
14
|
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
|
15
15
|
|
16
|
-
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
:custom_initializers => {},
|
18
|
+
:default_mode => nil,
|
19
|
+
:deserialize_symbols => false,
|
20
|
+
:whitelisted_tags => []
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
OPTIONS = DEFAULT_OPTIONS.dup
|
24
|
+
|
25
|
+
module_function
|
26
|
+
def reset_defaults!
|
27
|
+
OPTIONS.merge!(DEFAULT_OPTIONS)
|
28
|
+
end
|
21
29
|
end
|
22
30
|
|
23
31
|
module YAML
|
@@ -26,25 +34,25 @@ module YAML
|
|
26
34
|
safe_mode = safe_mode_from_options("load", options)
|
27
35
|
arguments = [yaml]
|
28
36
|
arguments << filename_and_options.first if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
29
|
-
safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
|
37
|
+
safe_mode == :safe ? safe_load(*arguments) : unsafe_load(*arguments)
|
30
38
|
end
|
31
39
|
|
32
40
|
def self.load_file_with_options(file, options={})
|
33
41
|
safe_mode = safe_mode_from_options("load_file", options)
|
34
|
-
safe_mode ? safe_load_file(file) : unsafe_load_file(file)
|
42
|
+
safe_mode == :safe ? safe_load_file(file) : unsafe_load_file(file)
|
35
43
|
end
|
36
44
|
|
37
45
|
if SafeYAML::YAML_ENGINE == "psych"
|
38
|
-
require "safe_yaml/
|
46
|
+
require "safe_yaml/psych_visitor"
|
47
|
+
require "safe_yaml/psych_resolver"
|
39
48
|
def self.safe_load(yaml, filename=nil)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
parser.parse(yaml, filename)
|
49
|
+
safe_resolver = SafeYAML::PsychResolver.new
|
50
|
+
tree = if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
51
|
+
Psych.parse(yaml, filename)
|
44
52
|
else
|
45
|
-
|
53
|
+
Psych.parse(yaml)
|
46
54
|
end
|
47
|
-
return
|
55
|
+
return safe_resolver.resolve_node(tree)
|
48
56
|
end
|
49
57
|
|
50
58
|
def self.safe_load_file(filename)
|
@@ -63,10 +71,12 @@ module YAML
|
|
63
71
|
|
64
72
|
else
|
65
73
|
require "safe_yaml/syck_resolver"
|
74
|
+
require "safe_yaml/syck_node_monkeypatch"
|
75
|
+
|
66
76
|
def self.safe_load(yaml)
|
67
|
-
|
77
|
+
resolver = SafeYAML::SyckResolver.new
|
68
78
|
tree = YAML.parse(yaml)
|
69
|
-
return
|
79
|
+
return resolver.resolve_node(tree)
|
70
80
|
end
|
71
81
|
|
72
82
|
def self.safe_load_file(filename)
|
@@ -85,40 +95,49 @@ module YAML
|
|
85
95
|
alias_method :load_file, :load_file_with_options
|
86
96
|
|
87
97
|
def enable_symbol_parsing?
|
88
|
-
SafeYAML::OPTIONS[:
|
98
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:deserialize_symbols] option instead")
|
99
|
+
SafeYAML::OPTIONS[:deserialize_symbols]
|
89
100
|
end
|
90
101
|
|
91
102
|
def enable_symbol_parsing!
|
92
|
-
SafeYAML::OPTIONS[:
|
103
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:deserialize_symbols] option instead")
|
104
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = true
|
93
105
|
end
|
94
106
|
|
95
107
|
def disable_symbol_parsing!
|
96
|
-
SafeYAML::OPTIONS[:
|
108
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:deserialize_symbols] option instead")
|
109
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = false
|
97
110
|
end
|
98
111
|
|
99
112
|
def enable_arbitrary_object_deserialization?
|
100
|
-
SafeYAML::OPTIONS[:
|
113
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:default_mode] to either :safe or :unsafe")
|
114
|
+
SafeYAML::OPTIONS[:default_mode] == :unsafe
|
101
115
|
end
|
102
116
|
|
103
117
|
def enable_arbitrary_object_deserialization!
|
104
|
-
SafeYAML::OPTIONS[:
|
118
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:default_mode] to either :safe or :unsafe")
|
119
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
105
120
|
end
|
106
121
|
|
107
122
|
def disable_arbitrary_object_deserialization!
|
108
|
-
SafeYAML::OPTIONS[:
|
123
|
+
warn_of_deprecated_method("set the SafeYAML::OPTIONS[:default_mode] to either :safe or :unsafe")
|
124
|
+
SafeYAML::OPTIONS[:default_mode] = :safe
|
109
125
|
end
|
110
126
|
|
111
127
|
private
|
112
128
|
def safe_mode_from_options(method, options={})
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode." unless SafeYAML::OPTIONS[:suppress_warnings]
|
118
|
-
safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
|
129
|
+
if options[:safe].nil?
|
130
|
+
safe_mode = SafeYAML::OPTIONS[:default_mode] || :safe
|
131
|
+
Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{safe_mode} mode." if SafeYAML::OPTIONS[:default_mode].nil?
|
132
|
+
return safe_mode
|
119
133
|
end
|
120
134
|
|
121
|
-
|
135
|
+
options[:safe] ? :safe : :unsafe
|
136
|
+
end
|
137
|
+
|
138
|
+
def warn_of_deprecated_method(message)
|
139
|
+
method = caller.first[/`([^']*)'$/, 1]
|
140
|
+
Kernel.warn("The method 'YAML.#{method}' is deprecated and will be removed in the next release of SafeYAML -- #{message}.")
|
122
141
|
end
|
123
142
|
end
|
124
143
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SafeYAML
|
2
|
+
class PsychResolver < Resolver
|
3
|
+
NODE_TYPES = {
|
4
|
+
Psych::Nodes::Document => :root,
|
5
|
+
Psych::Nodes::Mapping => :map,
|
6
|
+
Psych::Nodes::Sequence => :seq,
|
7
|
+
Psych::Nodes::Scalar => :scalar,
|
8
|
+
Psych::Nodes::Alias => :alias
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super()
|
13
|
+
@aliased_nodes = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def resolve_root(root)
|
17
|
+
resolve_seq(root).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve_alias(node)
|
21
|
+
resolve_node(@aliased_nodes[node.anchor])
|
22
|
+
end
|
23
|
+
|
24
|
+
def native_resolve(node)
|
25
|
+
@visitor ||= SafeYAML::PsychVisitor.new(self)
|
26
|
+
@visitor.accept(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_node_type(node)
|
30
|
+
NODE_TYPES[node.class]
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_node_tag(node)
|
34
|
+
node.tag
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_node_value(node)
|
38
|
+
@aliased_nodes[node.anchor] = node if node.respond_to?(:anchor) && node.anchor
|
39
|
+
|
40
|
+
case get_node_type(node)
|
41
|
+
when :root, :map, :seq
|
42
|
+
node.children
|
43
|
+
when :scalar
|
44
|
+
node.value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def value_is_quoted?(node)
|
49
|
+
node.quoted
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SafeYAML
|
2
|
+
class PsychVisitor < Psych::Visitors::ToRuby
|
3
|
+
def initialize(resolver)
|
4
|
+
super()
|
5
|
+
@resolver = resolver
|
6
|
+
end
|
7
|
+
|
8
|
+
def accept(node)
|
9
|
+
return super if @resolver.tag_is_whitelisted?(@resolver.get_node_tag(node))
|
10
|
+
@resolver.resolve_node(node)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module SafeYAML
|
2
|
+
class Resolver
|
3
|
+
def initialize
|
4
|
+
@whitelist = SafeYAML::OPTIONS[:whitelisted_tags] || []
|
5
|
+
@initializers = SafeYAML::OPTIONS[:custom_initializers] || {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def resolve_node(node)
|
9
|
+
case self.get_node_type(node)
|
10
|
+
when :root
|
11
|
+
resolve_root(node)
|
12
|
+
when :map
|
13
|
+
resolve_map(node)
|
14
|
+
when :seq
|
15
|
+
resolve_seq(node)
|
16
|
+
when :scalar
|
17
|
+
resolve_scalar(node)
|
18
|
+
when :alias
|
19
|
+
resolve_alias(node)
|
20
|
+
else
|
21
|
+
raise "Don't know how to resolve this node: #{node.inspect}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def resolve_map(node)
|
26
|
+
tag = self.get_node_tag(node)
|
27
|
+
return self.native_resolve(node) if tag_is_whitelisted?(tag)
|
28
|
+
|
29
|
+
hash = @initializers.include?(tag) ? @initializers[tag].call : {}
|
30
|
+
|
31
|
+
map = normalize_map(self.get_node_value(node))
|
32
|
+
|
33
|
+
# Take the "<<" key nodes first, as these are meant to approximate a form of inheritance.
|
34
|
+
inheritors = map.select { |key_node, value_node| resolve_node(key_node) == "<<" }
|
35
|
+
inheritors.each do |key_node, value_node|
|
36
|
+
merge_into_hash(hash, resolve_node(value_node))
|
37
|
+
end
|
38
|
+
|
39
|
+
# All that's left should be normal (non-"<<") nodes.
|
40
|
+
(map - inheritors).each do |key_node, value_node|
|
41
|
+
hash[resolve_node(key_node)] = resolve_node(value_node)
|
42
|
+
end
|
43
|
+
|
44
|
+
return hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve_seq(node)
|
48
|
+
seq = self.get_node_value(node)
|
49
|
+
|
50
|
+
tag = get_node_tag(node)
|
51
|
+
arr = @initializers.include?(tag) ? @initializers[tag].call : []
|
52
|
+
|
53
|
+
seq.inject(arr) { |array, node| array << resolve_node(node) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve_scalar(node)
|
57
|
+
Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), self.get_node_tag(node))
|
58
|
+
end
|
59
|
+
|
60
|
+
def tag_is_whitelisted?(tag)
|
61
|
+
@whitelist.include?(tag)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def normalize_map(map)
|
66
|
+
# Syck creates Hashes from maps.
|
67
|
+
if map.is_a?(Hash)
|
68
|
+
map.inject([]) { |arr, key_and_value| arr << key_and_value }
|
69
|
+
|
70
|
+
# Psych is really weird; it flattens out a Hash completely into: [key, value, key, value, ...]
|
71
|
+
else
|
72
|
+
map.each_slice(2).to_a
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def merge_into_hash(hash, array)
|
77
|
+
array.each do |key, value|
|
78
|
+
hash[key] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
monkeypatch = <<-EORUBY
|
2
|
+
class Node
|
3
|
+
def safe_transform
|
4
|
+
return unsafe_transform if SafeYAML::OPTIONS[:whitelisted_tags].include?(self.type_id)
|
5
|
+
SafeYAML::SyckResolver.new.resolve_node(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
alias_method :unsafe_transform, :transform
|
9
|
+
alias_method :transform, :safe_transform
|
10
|
+
end
|
11
|
+
EORUBY
|
12
|
+
|
13
|
+
if defined?(YAML::Syck::Node)
|
14
|
+
YAML::Syck.module_eval monkeypatch
|
15
|
+
else
|
16
|
+
Syck.module_eval monkeypatch
|
17
|
+
end
|
@@ -1,49 +1,35 @@
|
|
1
1
|
module SafeYAML
|
2
|
-
class SyckResolver
|
3
|
-
QUOTE_STYLES = [:quote1, :quote2]
|
2
|
+
class SyckResolver < Resolver
|
3
|
+
QUOTE_STYLES = [:quote1, :quote2].freeze
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
return resolve_seq(node)
|
11
|
-
when String
|
12
|
-
return resolve_scalar(node)
|
13
|
-
else
|
14
|
-
raise "Don't know how to resolve this node: #{node.inspect}"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def resolve_map(node)
|
19
|
-
map = node.value
|
5
|
+
NODE_TYPES = {
|
6
|
+
Hash => :map,
|
7
|
+
Array => :seq,
|
8
|
+
String => :scalar
|
9
|
+
}.freeze
|
20
10
|
|
21
|
-
|
11
|
+
def initialize
|
12
|
+
super()
|
13
|
+
end
|
22
14
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
value_node = map[key_node]
|
27
|
-
hash.merge!(resolve_node(value_node))
|
28
|
-
end
|
15
|
+
def native_resolve(node)
|
16
|
+
node.transform
|
17
|
+
end
|
29
18
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
value_node = map[key_node]
|
34
|
-
hash[resolve_node(key_node)] = resolve_node(value_node)
|
35
|
-
end
|
19
|
+
def get_node_type(node)
|
20
|
+
NODE_TYPES[node.value.class]
|
21
|
+
end
|
36
22
|
|
37
|
-
|
23
|
+
def get_node_tag(node)
|
24
|
+
node.type_id
|
38
25
|
end
|
39
26
|
|
40
|
-
def
|
41
|
-
|
42
|
-
seq.map { |node| resolve_node(node) }
|
27
|
+
def get_node_value(node)
|
28
|
+
node.value
|
43
29
|
end
|
44
30
|
|
45
|
-
def
|
46
|
-
|
31
|
+
def value_is_quoted?(node)
|
32
|
+
QUOTE_STYLES.include?(node.instance_variable_get(:@style))
|
47
33
|
end
|
48
34
|
end
|
49
35
|
end
|
data/lib/safe_yaml/transform.rb
CHANGED
@@ -24,15 +24,13 @@ module SafeYAML
|
|
24
24
|
|
25
25
|
value
|
26
26
|
end
|
27
|
-
|
28
|
-
def self.to_proper_type(value, quoted=false,tag=nil)
|
27
|
+
|
28
|
+
def self.to_proper_type(value, quoted=false, tag=nil)
|
29
29
|
case tag
|
30
|
-
when "tag:yaml.org,2002:binary"
|
31
|
-
|
32
|
-
when "x-private:binary"
|
33
|
-
return Base64.decode64(value)
|
30
|
+
when "tag:yaml.org,2002:binary", "x-private:binary", "!binary"
|
31
|
+
Base64.decode64(value)
|
34
32
|
else
|
35
|
-
|
33
|
+
self.to_guessed_type(value, quoted)
|
36
34
|
end
|
37
35
|
end
|
38
36
|
end
|
@@ -4,7 +4,7 @@ module SafeYAML
|
|
4
4
|
MATCHER = /\A:\w+\Z/.freeze
|
5
5
|
|
6
6
|
def transform?(value)
|
7
|
-
return false unless SafeYAML::OPTIONS[:
|
7
|
+
return false unless SafeYAML::OPTIONS[:deserialize_symbols] && MATCHER.match(value)
|
8
8
|
return true, value[1..-1].to_sym
|
9
9
|
end
|
10
10
|
end
|
data/lib/safe_yaml/version.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
|
-
module
|
1
|
+
module ResolverSpecs
|
2
2
|
def self.included(base)
|
3
|
-
base.
|
3
|
+
base.module_eval do
|
4
|
+
let(:resolver) { nil }
|
5
|
+
let(:result) { @result }
|
6
|
+
|
7
|
+
def parse(yaml)
|
8
|
+
tree = YAML.parse(yaml.unindent)
|
9
|
+
@result = resolver.resolve_node(tree)
|
10
|
+
end
|
11
|
+
|
4
12
|
context "by default" do
|
5
13
|
it "translates maps to hashes" do
|
6
14
|
parse <<-YAML
|
@@ -190,13 +198,13 @@ module SharedSpecs
|
|
190
198
|
end
|
191
199
|
end
|
192
200
|
|
193
|
-
context "with symbol
|
201
|
+
context "with symbol deserialization enabled" do
|
194
202
|
before :each do
|
195
|
-
|
203
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = true
|
196
204
|
end
|
197
205
|
|
198
206
|
after :each do
|
199
|
-
|
207
|
+
SafeYAML.reset_defaults!
|
200
208
|
end
|
201
209
|
|
202
210
|
it "translates values starting with ':' to symbols" do
|
data/spec/safe_yaml_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe YAML do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
before :each do
|
15
|
-
|
15
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = false
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "unsafe_load" do
|
@@ -32,6 +32,32 @@ describe YAML do
|
|
32
32
|
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
33
33
|
backdoor.should be_exploited_through_ivars
|
34
34
|
end
|
35
|
+
|
36
|
+
context "with special whitelisted tags defined" do
|
37
|
+
before :each do
|
38
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
39
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
|
40
|
+
else
|
41
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
after :each do
|
46
|
+
SafeYAML.reset_defaults!
|
47
|
+
end
|
48
|
+
|
49
|
+
it "effectively ignores the whitelist (since everything is whitelisted)" do
|
50
|
+
result = YAML.unsafe_load <<-YAML.unindent
|
51
|
+
--- !ruby/object:OpenStruct
|
52
|
+
table:
|
53
|
+
:backdoor: !ruby/object:ExploitableBackDoor
|
54
|
+
foo: bar
|
55
|
+
YAML
|
56
|
+
|
57
|
+
result.should be_a(OpenStruct)
|
58
|
+
result.backdoor.should be_exploited_through_ivars
|
59
|
+
end
|
60
|
+
end
|
35
61
|
end
|
36
62
|
|
37
63
|
describe "safe_load" do
|
@@ -54,12 +80,11 @@ describe YAML do
|
|
54
80
|
["foo: bar"]
|
55
81
|
end
|
56
82
|
}
|
83
|
+
|
57
84
|
it "uses Psych internally to parse YAML" do
|
58
|
-
|
59
|
-
Psych::Parser.stub(:new).and_return(stub_parser)
|
60
|
-
stub_parser.should_receive(:parse).with(*arguments)
|
85
|
+
Psych.should_receive(:parse).with(*arguments)
|
61
86
|
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
62
|
-
YAML.safe_load(*arguments)
|
87
|
+
YAML.safe_load(*arguments) rescue nil
|
63
88
|
end
|
64
89
|
end
|
65
90
|
|
@@ -127,6 +152,17 @@ describe YAML do
|
|
127
152
|
result.should == {"foo" => "bar", "bar" => "baz"}
|
128
153
|
end
|
129
154
|
|
155
|
+
it "works for YAML documents with binary tagged array values" do
|
156
|
+
result = YAML.safe_load <<-YAML
|
157
|
+
- !binary |-
|
158
|
+
Zm9v
|
159
|
+
- !binary |-
|
160
|
+
YmFy
|
161
|
+
YAML
|
162
|
+
|
163
|
+
result.should == ["foo", "bar"]
|
164
|
+
end
|
165
|
+
|
130
166
|
it "works for YAML documents with sections" do
|
131
167
|
result = YAML.safe_load <<-YAML
|
132
168
|
mysql: &mysql
|
@@ -203,6 +239,87 @@ describe YAML do
|
|
203
239
|
"grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
|
204
240
|
}
|
205
241
|
end
|
242
|
+
|
243
|
+
context "with custom initializers defined" do
|
244
|
+
before :each do
|
245
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
246
|
+
SafeYAML::OPTIONS[:custom_initializers] = {
|
247
|
+
"!set" => lambda { Set.new },
|
248
|
+
"!hashiemash" => lambda { Hashie::Mash.new }
|
249
|
+
}
|
250
|
+
else
|
251
|
+
SafeYAML::OPTIONS[:custom_initializers] = {
|
252
|
+
"tag:yaml.org,2002:set" => lambda { Set.new },
|
253
|
+
"tag:yaml.org,2002:hashiemash" => lambda { Hashie::Mash.new }
|
254
|
+
}
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
after :each do
|
259
|
+
SafeYAML.reset_defaults!
|
260
|
+
end
|
261
|
+
|
262
|
+
it "will use a custom initializer to instantiate an array-like class upon deserialization" do
|
263
|
+
result = YAML.safe_load <<-YAML.unindent
|
264
|
+
--- !set
|
265
|
+
- 1
|
266
|
+
- 2
|
267
|
+
- 3
|
268
|
+
YAML
|
269
|
+
|
270
|
+
result.should be_a(Set)
|
271
|
+
result.to_a.should =~ [1, 2, 3]
|
272
|
+
end
|
273
|
+
|
274
|
+
it "will use a custom initializer to instantiate a hash-like class upon deserialization" do
|
275
|
+
result = YAML.safe_load <<-YAML.unindent
|
276
|
+
--- !hashiemash
|
277
|
+
foo: bar
|
278
|
+
YAML
|
279
|
+
|
280
|
+
result.should be_a(Hashie::Mash)
|
281
|
+
result.to_hash.should == { "foo" => "bar" }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "with special whitelisted tags defined" do
|
286
|
+
before :each do
|
287
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
288
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
|
289
|
+
else
|
290
|
+
SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
after :each do
|
295
|
+
SafeYAML.reset_defaults!
|
296
|
+
end
|
297
|
+
|
298
|
+
it "will allow objects to be deserialized for whitelisted tags" do
|
299
|
+
result = YAML.safe_load("--- !ruby/object:OpenStruct\ntable:\n foo: bar\n")
|
300
|
+
result.should be_a(OpenStruct)
|
301
|
+
result.instance_variable_get(:@table).should == { "foo" => "bar" }
|
302
|
+
end
|
303
|
+
|
304
|
+
it "will not deserialize objects without whitelisted tags" do
|
305
|
+
result = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
306
|
+
result.should_not be_a(ExploitableBackDoor)
|
307
|
+
result.should == { "foo" => "bar" }
|
308
|
+
end
|
309
|
+
|
310
|
+
it "will not allow non-whitelisted objects to be embedded within objects with whitelisted tags" do
|
311
|
+
result = YAML.safe_load <<-YAML.unindent
|
312
|
+
--- !ruby/object:OpenStruct
|
313
|
+
table:
|
314
|
+
:backdoor: !ruby/object:ExploitableBackDoor
|
315
|
+
foo: bar
|
316
|
+
YAML
|
317
|
+
|
318
|
+
result.should be_a(OpenStruct)
|
319
|
+
result.backdoor.should_not be_a(ExploitableBackDoor)
|
320
|
+
result.instance_variable_get(:@table).should == { ":backdoor" => { "foo" => "bar" } }
|
321
|
+
end
|
322
|
+
end
|
206
323
|
end
|
207
324
|
|
208
325
|
describe "unsafe_load_file" do
|
@@ -247,12 +364,19 @@ describe YAML do
|
|
247
364
|
end
|
248
365
|
}
|
249
366
|
|
250
|
-
context "
|
251
|
-
|
252
|
-
|
367
|
+
context "as long as a :default_mode has been specified" do
|
368
|
+
after :each do
|
369
|
+
SafeYAML.reset_defaults!
|
370
|
+
end
|
371
|
+
|
372
|
+
it "doesn't issue a warning for safe mode, since an explicit mode has been set" do
|
373
|
+
SafeYAML::OPTIONS[:default_mode] = :safe
|
374
|
+
Kernel.should_not_receive(:warn)
|
375
|
+
YAML.load(*arguments)
|
376
|
+
end
|
253
377
|
|
254
|
-
it "doesn't issue a warning
|
255
|
-
SafeYAML::OPTIONS[:
|
378
|
+
it "doesn't issue a warning for unsafe mode, since an explicit mode has been set" do
|
379
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
256
380
|
Kernel.should_not_receive(:warn)
|
257
381
|
YAML.load(*arguments)
|
258
382
|
end
|
@@ -289,11 +413,11 @@ describe YAML do
|
|
289
413
|
|
290
414
|
context "with arbitrary object deserialization enabled by default" do
|
291
415
|
before :each do
|
292
|
-
|
416
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
293
417
|
end
|
294
418
|
|
295
419
|
after :each do
|
296
|
-
|
420
|
+
SafeYAML.reset_defaults!
|
297
421
|
end
|
298
422
|
|
299
423
|
it "defaults to unsafe mode if the :safe option is omitted" do
|
@@ -344,11 +468,11 @@ describe YAML do
|
|
344
468
|
|
345
469
|
context "with arbitrary object deserialization enabled by default" do
|
346
470
|
before :each do
|
347
|
-
|
471
|
+
SafeYAML::OPTIONS[:default_mode] = :unsafe
|
348
472
|
end
|
349
473
|
|
350
474
|
after :each do
|
351
|
-
|
475
|
+
SafeYAML.reset_defaults!
|
352
476
|
end
|
353
477
|
|
354
478
|
it "defaults to unsafe mode if the :safe option is omitted" do
|
data/spec/spec_helper.rb
CHANGED
data/spec/syck_resolver_spec.rb
CHANGED
@@ -4,14 +4,7 @@ if SafeYAML::YAML_ENGINE == "syck"
|
|
4
4
|
require "safe_yaml/syck_resolver"
|
5
5
|
|
6
6
|
describe SafeYAML::SyckResolver do
|
7
|
+
include ResolverSpecs
|
7
8
|
let(:resolver) { SafeYAML::SyckResolver.new }
|
8
|
-
let(:result) { @result }
|
9
|
-
|
10
|
-
def parse(yaml)
|
11
|
-
tree = YAML.parse(yaml.unindent)
|
12
|
-
@result = resolver.resolve_node(tree)
|
13
|
-
end
|
14
|
-
|
15
|
-
include SharedSpecs
|
16
9
|
end
|
17
10
|
end
|
@@ -1,44 +1,44 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
2
|
|
3
3
|
describe SafeYAML::Transform::ToSymbol do
|
4
|
-
def
|
5
|
-
|
6
|
-
SafeYAML::OPTIONS[:
|
4
|
+
def with_symbol_deserialization_value(value)
|
5
|
+
symbol_deserialization_flag = SafeYAML::OPTIONS[:deserialize_symbols]
|
6
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = value
|
7
7
|
|
8
8
|
yield
|
9
9
|
|
10
10
|
ensure
|
11
|
-
SafeYAML::OPTIONS[:
|
11
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = symbol_deserialization_flag
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
14
|
+
def with_symbol_deserialization(&block)
|
15
|
+
with_symbol_deserialization_value(true, &block)
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def without_symbol_deserialization(&block)
|
19
|
+
with_symbol_deserialization_value(false, &block)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "returns true when the value matches a valid Symbol" do
|
23
|
-
|
23
|
+
with_symbol_deserialization { subject.transform?(":foo")[0].should be_true }
|
24
24
|
end
|
25
25
|
|
26
|
-
it "returns false when symbol
|
27
|
-
|
26
|
+
it "returns false when symbol deserialization is disabled" do
|
27
|
+
without_symbol_deserialization { subject.transform?(":foo").should be_false }
|
28
28
|
end
|
29
29
|
|
30
30
|
it "returns false when the value does not match a valid Symbol" do
|
31
|
-
|
31
|
+
with_symbol_deserialization { subject.transform?("foo").should be_false }
|
32
32
|
end
|
33
33
|
|
34
34
|
it "returns false when the symbol does not begin the line" do
|
35
|
-
|
35
|
+
with_symbol_deserialization do
|
36
36
|
subject.transform?("NOT A SYMBOL\n:foo").should be_false
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
40
|
it "returns false when the symbol does not end the line" do
|
41
|
-
|
41
|
+
with_symbol_deserialization do
|
42
42
|
subject.transform?(":foo\nNOT A SYMBOL").should be_false
|
43
43
|
end
|
44
44
|
end
|
metadata
CHANGED
@@ -1,23 +1,32 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_yaml
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 8
|
9
|
+
- 0
|
10
|
+
version: 0.8.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Dan Tao
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
17
|
+
|
18
|
+
date: 2013-02-17 00:00:00 Z
|
13
19
|
dependencies: []
|
14
|
-
|
15
|
-
|
20
|
+
|
21
|
+
description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
|
16
22
|
email: daniel.tao@gmail.com
|
17
23
|
executables: []
|
24
|
+
|
18
25
|
extensions: []
|
26
|
+
|
19
27
|
extra_rdoc_files: []
|
20
|
-
|
28
|
+
|
29
|
+
files:
|
21
30
|
- .gitignore
|
22
31
|
- .travis.yml
|
23
32
|
- Gemfile
|
@@ -25,7 +34,10 @@ files:
|
|
25
34
|
- README.md
|
26
35
|
- Rakefile
|
27
36
|
- lib/safe_yaml.rb
|
28
|
-
- lib/safe_yaml/
|
37
|
+
- lib/safe_yaml/psych_resolver.rb
|
38
|
+
- lib/safe_yaml/psych_visitor.rb
|
39
|
+
- lib/safe_yaml/resolver.rb
|
40
|
+
- lib/safe_yaml/syck_node_monkeypatch.rb
|
29
41
|
- lib/safe_yaml/syck_resolver.rb
|
30
42
|
- lib/safe_yaml/transform.rb
|
31
43
|
- lib/safe_yaml/transform/to_boolean.rb
|
@@ -40,9 +52,9 @@ files:
|
|
40
52
|
- safe_yaml.gemspec
|
41
53
|
- spec/exploit.1.9.2.yaml
|
42
54
|
- spec/exploit.1.9.3.yaml
|
43
|
-
- spec/
|
55
|
+
- spec/psych_resolver_spec.rb
|
56
|
+
- spec/resolver_specs.rb
|
44
57
|
- spec/safe_yaml_spec.rb
|
45
|
-
- spec/shared_specs.rb
|
46
58
|
- spec/spec_helper.rb
|
47
59
|
- spec/support/exploitable_back_door.rb
|
48
60
|
- spec/syck_resolver_spec.rb
|
@@ -52,37 +64,46 @@ files:
|
|
52
64
|
- spec/transform/to_symbol_spec.rb
|
53
65
|
- spec/transform/to_time_spec.rb
|
54
66
|
homepage: http://dtao.github.com/safe_yaml/
|
55
|
-
licenses:
|
67
|
+
licenses:
|
56
68
|
- MIT
|
57
69
|
post_install_message:
|
58
70
|
rdoc_options: []
|
59
|
-
|
71
|
+
|
72
|
+
require_paths:
|
60
73
|
- lib
|
61
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
75
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 57
|
80
|
+
segments:
|
81
|
+
- 1
|
82
|
+
- 8
|
83
|
+
- 7
|
66
84
|
version: 1.8.7
|
67
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
86
|
none: false
|
69
|
-
requirements:
|
70
|
-
- -
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
73
94
|
requirements: []
|
95
|
+
|
74
96
|
rubyforge_project:
|
75
97
|
rubygems_version: 1.8.25
|
76
98
|
signing_key:
|
77
99
|
specification_version: 3
|
78
|
-
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
79
|
-
|
80
|
-
test_files:
|
100
|
+
summary: SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.
|
101
|
+
test_files:
|
81
102
|
- spec/exploit.1.9.2.yaml
|
82
103
|
- spec/exploit.1.9.3.yaml
|
83
|
-
- spec/
|
104
|
+
- spec/psych_resolver_spec.rb
|
105
|
+
- spec/resolver_specs.rb
|
84
106
|
- spec/safe_yaml_spec.rb
|
85
|
-
- spec/shared_specs.rb
|
86
107
|
- spec/spec_helper.rb
|
87
108
|
- spec/support/exploitable_back_door.rb
|
88
109
|
- spec/syck_resolver_spec.rb
|
@@ -1,91 +0,0 @@
|
|
1
|
-
require "psych"
|
2
|
-
require "base64"
|
3
|
-
|
4
|
-
module SafeYAML
|
5
|
-
class PsychHandler < Psych::Handler
|
6
|
-
def initialize
|
7
|
-
@anchors = {}
|
8
|
-
@stack = []
|
9
|
-
@current_key = nil
|
10
|
-
@result = nil
|
11
|
-
end
|
12
|
-
|
13
|
-
def result
|
14
|
-
@result
|
15
|
-
end
|
16
|
-
|
17
|
-
def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
|
18
|
-
value = Transform.to_proper_type(value, quoted, tag)
|
19
|
-
|
20
|
-
@anchors[anchor] = value if anchor
|
21
|
-
|
22
|
-
if @result.nil?
|
23
|
-
@result = value
|
24
|
-
@current_structure = @result
|
25
|
-
return
|
26
|
-
end
|
27
|
-
|
28
|
-
case @current_structure
|
29
|
-
when Array
|
30
|
-
@current_structure.push(value)
|
31
|
-
|
32
|
-
when Hash
|
33
|
-
if @current_key.nil?
|
34
|
-
@current_key = value
|
35
|
-
|
36
|
-
else
|
37
|
-
if @current_key == "<<"
|
38
|
-
@current_structure.merge!(value)
|
39
|
-
else
|
40
|
-
@current_structure[@current_key] = value
|
41
|
-
end
|
42
|
-
|
43
|
-
@current_key = nil
|
44
|
-
end
|
45
|
-
|
46
|
-
else
|
47
|
-
raise "Don't know how to add to a #{@current_structure.class}!"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def end_current_structure
|
52
|
-
@stack.pop
|
53
|
-
@current_structure = @stack.last
|
54
|
-
end
|
55
|
-
|
56
|
-
def streaming?
|
57
|
-
false
|
58
|
-
end
|
59
|
-
|
60
|
-
# event handlers
|
61
|
-
def alias(anchor)
|
62
|
-
add_to_current_structure(@anchors[anchor])
|
63
|
-
end
|
64
|
-
|
65
|
-
def scalar(value, anchor, tag, plain, quoted, style)
|
66
|
-
add_to_current_structure(value, anchor, quoted, tag)
|
67
|
-
end
|
68
|
-
|
69
|
-
def start_mapping(anchor, tag, implicit, style)
|
70
|
-
map = {}
|
71
|
-
self.add_to_current_structure(map, anchor)
|
72
|
-
@current_structure = map
|
73
|
-
@stack.push(map)
|
74
|
-
end
|
75
|
-
|
76
|
-
def end_mapping
|
77
|
-
self.end_current_structure()
|
78
|
-
end
|
79
|
-
|
80
|
-
def start_sequence(anchor, tag, implicit, style)
|
81
|
-
seq = []
|
82
|
-
self.add_to_current_structure(seq, anchor)
|
83
|
-
@current_structure = seq
|
84
|
-
@stack.push(seq)
|
85
|
-
end
|
86
|
-
|
87
|
-
def end_sequence
|
88
|
-
self.end_current_structure()
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
data/spec/psych_handler_spec.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
-
|
3
|
-
if SafeYAML::YAML_ENGINE == "psych"
|
4
|
-
require "safe_yaml/psych_handler"
|
5
|
-
|
6
|
-
describe SafeYAML::PsychHandler do
|
7
|
-
let(:handler) { SafeYAML::PsychHandler.new }
|
8
|
-
let(:parser) { Psych::Parser.new(handler) }
|
9
|
-
let(:result) { handler.result }
|
10
|
-
|
11
|
-
def parse(yaml)
|
12
|
-
parser.parse(yaml.unindent)
|
13
|
-
end
|
14
|
-
|
15
|
-
include SharedSpecs
|
16
|
-
end
|
17
|
-
end
|