safe_yaml 0.8.3 → 0.8.4

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.
data/lib/safe_yaml.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  require "yaml"
2
+ require "safe_yaml/parse/hexadecimal"
3
+ require "safe_yaml/parse/sexagesimal"
4
+ require "safe_yaml/parse/date"
2
5
  require "safe_yaml/transform/to_boolean"
3
6
  require "safe_yaml/transform/to_date"
4
7
  require "safe_yaml/transform/to_float"
5
8
  require "safe_yaml/transform/to_integer"
6
9
  require "safe_yaml/transform/to_nil"
7
10
  require "safe_yaml/transform/to_symbol"
8
- require "safe_yaml/transform/to_time"
9
11
  require "safe_yaml/transform"
10
12
  require "safe_yaml/resolver"
11
13
 
@@ -41,7 +43,12 @@ module SafeYAML
41
43
  end
42
44
 
43
45
  else
44
- TRUSTED_TAGS = ["tag:yaml.org,2002:str"].freeze
46
+ TRUSTED_TAGS = [
47
+ "tag:yaml.org,2002:str",
48
+ "tag:yaml.org,2002:int",
49
+ "tag:yaml.org,2002:float#fix",
50
+ "tag:yaml.org,2002:timestamp#ymd"
51
+ ].freeze
45
52
 
46
53
  def tag_is_explicitly_trusted?(tag)
47
54
  TRUSTED_TAGS.include?(tag)
@@ -0,0 +1,27 @@
1
+ module SafeYAML
2
+ class Parse
3
+ class Date
4
+ # This one's easy enough :)
5
+ DATE_MATCHER = /\A(\d{4})-(\d{2})-(\d{2})\Z/.freeze
6
+
7
+ # This unbelievable little gem is taken basically straight from the YAML spec, but made
8
+ # slightly more readable (to my poor eyes at least) to me:
9
+ # http://yaml.org/type/timestamp.html
10
+ TIME_MATCHER = /\A\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d{2}:\d{2}(?:\.\d*)?\s*(?:Z|[-+]\d{1,2}(?::?\d{2})?)?\Z/.freeze
11
+
12
+ SECONDS_PER_DAY = 60 * 60 * 24
13
+ MICROSECONDS_PER_SECOND = 1000000
14
+
15
+ # So this is weird. In Ruby 1.8.7, the DateTime#sec_fraction method returned fractional
16
+ # seconds in units of DAYS for some reason. In 1.9.2, they changed the units -- much more
17
+ # reasonably -- to seconds.
18
+ SEC_FRACTION_MULTIPLIER = RUBY_VERSION == "1.8.7" ? (SECONDS_PER_DAY * MICROSECONDS_PER_SECOND) : MICROSECONDS_PER_SECOND
19
+
20
+ def self.value(value)
21
+ d = DateTime.parse(value)
22
+ usec = d.sec_fraction * SEC_FRACTION_MULTIPLIER
23
+ Time.utc(d.year, d.month, d.day, d.hour, d.min, d.sec, usec) - (d.offset * SECONDS_PER_DAY)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module SafeYAML
2
+ class Parse
3
+ class Hexadecimal
4
+ MATCHER = /\A[-+]?0x[0-9a-fA-F_]+\Z/.freeze
5
+
6
+ def self.value(value)
7
+ # This is safe to do since we already validated the value.
8
+ return Integer(value.gsub(/_/, ""))
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module SafeYAML
2
+ class Parse
3
+ class Sexagesimal
4
+ INTEGER_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\Z/.freeze
5
+ FLOAT_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*\Z/.freeze
6
+
7
+ def self.value(value)
8
+ before_decimal, after_decimal = value.split(".")
9
+
10
+ whole_part = 0
11
+ multiplier = 1
12
+
13
+ before_decimal = before_decimal.split(":")
14
+ until before_decimal.empty?
15
+ whole_part += (Float(before_decimal.pop) * multiplier)
16
+ multiplier *= 60
17
+ end
18
+
19
+ result = whole_part
20
+ result += Float("." + after_decimal) unless after_decimal.nil?
21
+ result *= -1 if value[0] == "-"
22
+ result
23
+ end
24
+ end
25
+ end
26
+ end
@@ -8,8 +8,7 @@ module SafeYAML
8
8
  Transform::ToFloat.new,
9
9
  Transform::ToNil.new,
10
10
  Transform::ToBoolean.new,
11
- Transform::ToDate.new,
12
- Transform::ToTime.new
11
+ Transform::ToDate.new
13
12
  ]
14
13
 
15
14
  def self.to_guessed_type(value, quoted=false)
@@ -28,7 +27,9 @@ module SafeYAML
28
27
  def self.to_proper_type(value, quoted=false, tag=nil)
29
28
  case tag
30
29
  when "tag:yaml.org,2002:binary", "x-private:binary", "!binary"
31
- Base64.decode64(value)
30
+ decoded = Base64.decode64(value)
31
+ decoded = decoded.force_encoding(value.encoding) if decoded.respond_to?(:force_encoding)
32
+ decoded
32
33
  else
33
34
  self.to_guessed_type(value, quoted)
34
35
  end
@@ -1,12 +1,10 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToDate
4
- MATCHER = /\A\d{4}\-\d{2}\-\d{2}\Z/.freeze
5
-
6
4
  def transform?(value)
7
- return false unless MATCHER.match(value)
8
- date = Date.parse(value) rescue nil
9
- return !!date, date
5
+ return true, Date.parse(value) if Parse::Date::DATE_MATCHER.match(value)
6
+ return true, Parse::Date.value(value) if Parse::Date::TIME_MATCHER.match(value)
7
+ false
10
8
  end
11
9
  end
12
10
  end
@@ -1,11 +1,29 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToFloat
4
- MATCHER = /\A\d*\.\d+\Z/.freeze
4
+ Infinity = 1.0 / 0.0
5
+ NaN = 0.0 / 0.0
6
+
7
+ PREDEFINED_VALUES = {
8
+ ".inf" => Infinity,
9
+ ".Inf" => Infinity,
10
+ ".INF" => Infinity,
11
+ "-.inf" => -Infinity,
12
+ "-.Inf" => -Infinity,
13
+ "-.INF" => -Infinity,
14
+ ".nan" => NaN,
15
+ ".NaN" => NaN,
16
+ ".NAN" => NaN,
17
+ }.freeze
5
18
 
6
19
  def transform?(value)
7
- return false unless MATCHER.match(value)
8
- return true, value.to_f
20
+ return true, Float(value) rescue try_edge_cases?(value)
21
+ end
22
+
23
+ def try_edge_cases?(value)
24
+ return true, PREDEFINED_VALUES[value] if PREDEFINED_VALUES.include?(value)
25
+ return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::FLOAT_MATCHER.match(value)
26
+ return false
9
27
  end
10
28
  end
11
29
  end
@@ -1,17 +1,14 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToInteger
4
- OCTAL_MATCHER = /\A0[0-7]+\Z/.freeze
5
- HEXADECIMAL_MATCHER = /\A0x[0-9a-f]+\Z/i.freeze
6
- MATCHER = /\A[1-9]\d*\Z/.freeze
7
-
8
4
  def transform?(value)
9
- if OCTAL_MATCHER.match(value) || HEXADECIMAL_MATCHER.match(value)
10
- return true, Integer(value)
11
- end
5
+ return true, Integer(value) rescue try_edge_cases?(value)
6
+ end
12
7
 
13
- return false unless MATCHER.match(value)
14
- return true, value.to_i
8
+ def try_edge_cases?(value)
9
+ return true, Parse::Hexadecimal.value(value) if Parse::Hexadecimal::MATCHER.match(value)
10
+ return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::INTEGER_MATCHER.match(value)
11
+ return false
15
12
  end
16
13
  end
17
14
  end
@@ -1,11 +1,11 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToSymbol
4
- MATCHER = /\A:\w+\Z/.freeze
4
+ MATCHER = /\A:"?(\w+)"?\Z/.freeze
5
5
 
6
6
  def transform?(value)
7
7
  return false unless SafeYAML::OPTIONS[:deserialize_symbols] && MATCHER.match(value)
8
- return true, value[1..-1].to_sym
8
+ return true, $1.to_sym
9
9
  end
10
10
  end
11
11
  end
@@ -1,3 +1,3 @@
1
1
  module SafeYAML
2
- VERSION = "0.8.3"
2
+ VERSION = "0.8.4"
3
3
  end
@@ -171,29 +171,20 @@ module ResolverSpecs
171
171
 
172
172
  # This does in fact appear to be a Ruby version thing as opposed to a YAML engine thing.
173
173
  context "for Ruby version #{RUBY_VERSION}" do
174
- if RUBY_VERSION >= "1.9.2"
174
+ if RUBY_VERSION >= "1.8.7"
175
175
  it "translates valid time values" do
176
176
  parse "time: 2013-01-29 05:58:00 -0800"
177
- result.should == { "time" => Time.new(2013, 1, 29, 5, 58, 0, "-08:00") }
177
+ result.should == { "time" => Time.utc(2013, 1, 29, 13, 58, 0) }
178
178
  end
179
179
 
180
180
  it "applies the same transformation to elements in sequences" do
181
181
  parse "- 2013-01-29 05:58:00 -0800"
182
- result.should == [Time.new(2013, 1, 29, 5, 58, 0, "-08:00")]
182
+ result.should == [Time.utc(2013, 1, 29, 13, 58, 0)]
183
183
  end
184
184
 
185
- # On Ruby 2.0.0-rc1, even YAML.load overflows the stack on this input.
186
- if RUBY_VERSION != "2.0.0"
187
- it "applies the same transformation to keys" do
188
- parse "2013-01-29 05:58:00 -0800: time"
189
- result.should == { Time.new(2013, 1, 29, 5, 58, 0, "-08:00") => "time" }
190
- end
191
- end
192
-
193
- else
194
- it "does not deserialize times" do
195
- parse "time: 2013-01-29 05:58:00 -0800"
196
- result.should == { "time" => "2013-01-29 05:58:00 -0800" }
185
+ it "applies the same transformation to keys" do
186
+ parse "2013-01-29 05:58:00 -0800: time"
187
+ result.should == { Time.utc(2013, 1, 29, 13, 58, 0) => "time" }
197
188
  end
198
189
  end
199
190
  end
@@ -362,11 +362,25 @@ describe YAML do
362
362
  --- !ruby/object:OpenStruct
363
363
  table:
364
364
  :backdoor:
365
- foo: bar
365
+ string: foo
366
+ integer: 1
367
+ float: 3.14
368
+ symbol: :bar
369
+ date: 2013-02-20
370
+ array: []
371
+ hash: {}
366
372
  YAML
367
373
 
368
374
  result.should be_a(OpenStruct)
369
- result.backdoor.should == { "foo" => "bar" }
375
+ result.backdoor.should == {
376
+ "string" => "foo",
377
+ "integer" => 1,
378
+ "float" => 3.14,
379
+ "symbol" => :bar,
380
+ "date" => Date.parse("2013-02-20"),
381
+ "array" => [],
382
+ "hash" => {}
383
+ }
370
384
  end
371
385
  end
372
386
  end
data/spec/spec_helper.rb CHANGED
@@ -4,8 +4,8 @@ ROOT = File.join(HERE, "..") unless defined?(ROOT)
4
4
  $LOAD_PATH << File.join(ROOT, "lib")
5
5
  $LOAD_PATH << File.join(HERE, "support")
6
6
 
7
+ require "yaml"
7
8
  if ENV["YAMLER"] && defined?(YAML::ENGINE)
8
- require "yaml"
9
9
  YAML::ENGINE.yamler = ENV["YAMLER"]
10
10
  puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
11
11
  end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform do
4
+ it "should return the same encoding when decoding Base64" do
5
+ value = "c3VyZS4="
6
+ decoded = SafeYAML::Transform.to_proper_type(value, false, "!binary")
7
+
8
+ decoded.should == "sure."
9
+ decoded.encoding.should == value.encoding if decoded.respond_to?(:encoding)
10
+ end
11
+ end
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
2
 
3
3
  describe SafeYAML::Transform::ToDate do
4
4
  it "returns true when the value matches a valid Date" do
5
- subject.transform?("2013-01-01")[0].should == true
5
+ subject.transform?("2013-01-01").should == [true, Date.parse("2013-01-01")]
6
6
  end
7
7
 
8
8
  it "returns false when the value does not match a valid Date" do
@@ -16,4 +16,19 @@ describe SafeYAML::Transform::ToDate do
16
16
  it "returns false when the value does not begin with a Date" do
17
17
  subject.transform?("NOT A DATE\n2013-01-01").should be_false
18
18
  end
19
+
20
+ it "correctly parses the remaining formats of the YAML spec" do
21
+ equivalent_values = [
22
+ "2001-12-15T02:59:43.1Z", # canonical
23
+ "2001-12-14t21:59:43.10-05:00", # iso8601
24
+ "2001-12-14 21:59:43.10 -5", # space separated
25
+ "2001-12-15 2:59:43.10" # no time zone (Z)
26
+ ]
27
+
28
+ equivalent_values.each do |value|
29
+ success, result = subject.transform?(value)
30
+ success.should be_true
31
+ result.should == Time.utc(2001, 12, 15, 2, 59, 43, 100000)
32
+ end
33
+ end
19
34
  end
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
2
 
3
3
  describe SafeYAML::Transform::ToFloat do
4
4
  it "returns true when the value matches a valid Float" do
5
- subject.transform?("20.00").should be_true
5
+ subject.transform?("20.00").should == [true, 20.0]
6
6
  end
7
7
 
8
8
  it "returns false when the value does not match a valid Float" do
@@ -12,4 +12,26 @@ describe SafeYAML::Transform::ToFloat do
12
12
  it "returns false when the value spans multiple lines" do
13
13
  subject.transform?("20.00\nNOT A FLOAT").should be_false
14
14
  end
15
+
16
+ it "correctly parses all formats in the YAML spec" do
17
+ # canonical
18
+ subject.transform?("6.8523015e+5").should == [true, 685230.15]
19
+
20
+ # exponentioal
21
+ subject.transform?("685.230_15e+03").should == [true, 685230.15]
22
+
23
+ # fixed
24
+ subject.transform?("685_230.15").should == [true, 685230.15]
25
+
26
+ # sexagesimal
27
+ subject.transform?("190:20:30.15").should == [true, 685230.15]
28
+
29
+ # infinity
30
+ subject.transform?("-.inf").should == [true, (-1.0 / 0.0)]
31
+
32
+ # not a number
33
+ # NOTE: can't use == here since NaN != NaN
34
+ success, result = subject.transform?(".NaN")
35
+ success.should be_true; result.should be_nan
36
+ end
15
37
  end
@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
2
 
3
3
  describe SafeYAML::Transform::ToInteger do
4
4
  it "returns true when the value matches a valid Integer" do
5
- subject.transform?("10").should be_true
5
+ subject.transform?("10").should == [true, 10]
6
6
  end
7
7
 
8
8
  it "returns false when the value does not match a valid Integer" do
@@ -28,4 +28,24 @@ describe SafeYAML::Transform::ToInteger do
28
28
  it "defaults to a string for a number that resembles hexadecimal format but is not" do
29
29
  subject.transform?("0x1G").should be_false
30
30
  end
31
+
32
+ it "correctly parses all formats in the YAML spec" do
33
+ # canonical
34
+ subject.transform?("685230").should == [true, 685230]
35
+
36
+ # decimal
37
+ subject.transform?("+685_230").should == [true, 685230]
38
+
39
+ # octal
40
+ subject.transform?("02472256").should == [true, 685230]
41
+
42
+ # hexadecimal:
43
+ subject.transform?("0x_0A_74_AE").should == [true, 685230]
44
+
45
+ # binary
46
+ subject.transform?("0b1010_0111_0100_1010_1110").should == [true, 685230]
47
+
48
+ # sexagesimal
49
+ subject.transform?("190:20:30").should == [true, 685230]
50
+ end
31
51
  end
@@ -23,6 +23,10 @@ describe SafeYAML::Transform::ToSymbol do
23
23
  with_symbol_deserialization { subject.transform?(":foo")[0].should be_true }
24
24
  end
25
25
 
26
+ it "returns true when the value matches a valid String+Symbol" do
27
+ with_symbol_deserialization { subject.transform?(':"foo"')[0].should be_true }
28
+ end
29
+
26
30
  it "returns false when symbol deserialization is disabled" do
27
31
  without_symbol_deserialization { subject.transform?(":foo").should be_false }
28
32
  end
metadata CHANGED
@@ -1,32 +1,23 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: safe_yaml
3
- version: !ruby/object:Gem::Version
4
- hash: 57
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.4
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 8
9
- - 3
10
- version: 0.8.3
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Dan Tao
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2013-02-20 00:00:00 Z
12
+ date: 2013-02-26 00:00:00.000000000 Z
19
13
  dependencies: []
20
-
21
- description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
14
+ description: Parse YAML safely, without that pesky arbitrary object deserialization
15
+ vulnerability
22
16
  email: daniel.tao@gmail.com
23
17
  executables: []
24
-
25
18
  extensions: []
26
-
27
19
  extra_rdoc_files: []
28
-
29
- files:
20
+ files:
30
21
  - .gitignore
31
22
  - .travis.yml
32
23
  - Gemfile
@@ -34,6 +25,9 @@ files:
34
25
  - README.md
35
26
  - Rakefile
36
27
  - lib/safe_yaml.rb
28
+ - lib/safe_yaml/parse/date.rb
29
+ - lib/safe_yaml/parse/hexadecimal.rb
30
+ - lib/safe_yaml/parse/sexagesimal.rb
37
31
  - lib/safe_yaml/psych_resolver.rb
38
32
  - lib/safe_yaml/resolver.rb
39
33
  - lib/safe_yaml/safe_to_ruby_visitor.rb
@@ -46,7 +40,6 @@ files:
46
40
  - lib/safe_yaml/transform/to_integer.rb
47
41
  - lib/safe_yaml/transform/to_nil.rb
48
42
  - lib/safe_yaml/transform/to_symbol.rb
49
- - lib/safe_yaml/transform/to_time.rb
50
43
  - lib/safe_yaml/version.rb
51
44
  - run_specs_all_ruby_versions.sh
52
45
  - safe_yaml.gemspec
@@ -58,47 +51,38 @@ files:
58
51
  - spec/spec_helper.rb
59
52
  - spec/support/exploitable_back_door.rb
60
53
  - spec/syck_resolver_spec.rb
54
+ - spec/transform/base64_spec.rb
61
55
  - spec/transform/to_date_spec.rb
62
56
  - spec/transform/to_float_spec.rb
63
57
  - spec/transform/to_integer_spec.rb
64
58
  - spec/transform/to_symbol_spec.rb
65
- - spec/transform/to_time_spec.rb
66
59
  homepage: http://dtao.github.com/safe_yaml/
67
- licenses:
60
+ licenses:
68
61
  - MIT
69
62
  post_install_message:
70
63
  rdoc_options: []
71
-
72
- require_paths:
64
+ require_paths:
73
65
  - lib
74
- required_ruby_version: !ruby/object:Gem::Requirement
66
+ required_ruby_version: !ruby/object:Gem::Requirement
75
67
  none: false
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- hash: 57
80
- segments:
81
- - 1
82
- - 8
83
- - 7
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
84
71
  version: 1.8.7
85
- required_rubygems_version: !ruby/object:Gem::Requirement
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
73
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
94
78
  requirements: []
95
-
96
79
  rubyforge_project:
97
80
  rubygems_version: 1.8.25
98
81
  signing_key:
99
82
  specification_version: 3
100
- summary: SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.
101
- test_files:
83
+ summary: SameYAML provides an alternative implementation of YAML.load suitable for
84
+ accepting user input in Ruby applications.
85
+ test_files:
102
86
  - spec/exploit.1.9.2.yaml
103
87
  - spec/exploit.1.9.3.yaml
104
88
  - spec/psych_resolver_spec.rb
@@ -107,8 +91,8 @@ test_files:
107
91
  - spec/spec_helper.rb
108
92
  - spec/support/exploitable_back_door.rb
109
93
  - spec/syck_resolver_spec.rb
94
+ - spec/transform/base64_spec.rb
110
95
  - spec/transform/to_date_spec.rb
111
96
  - spec/transform/to_float_spec.rb
112
97
  - spec/transform/to_integer_spec.rb
113
98
  - spec/transform/to_symbol_spec.rb
114
- - spec/transform/to_time_spec.rb
@@ -1,18 +0,0 @@
1
- module SafeYAML
2
- class Transform
3
- class ToTime
4
- # There isn't a missing '$' there; YAML itself seems to ignore everything at the end of a
5
- # string that otherwise resembles a time.
6
- MATCHER = /\A\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,5})?/.freeze
7
-
8
- def transform?(value)
9
- return false unless MATCHER.match(value)
10
- datetime = DateTime.parse(value) rescue nil
11
- if datetime.respond_to?(:to_time)
12
- return true, datetime.to_time
13
- end
14
- false
15
- end
16
- end
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
-
3
- describe SafeYAML::Transform::ToTime do
4
- # It seems both Psych and Syck parse times starting w/ Ruby 1.9.2.
5
- if RUBY_VERSION >= "1.9.2"
6
- it "returns true when the value matches a valid Time" do
7
- subject.transform?("2013-01-01 10:00:00")[0].should == true
8
- end
9
- end
10
-
11
- it "returns false when the value does not match a valid Time" do
12
- subject.transform?("not a time").should be_false
13
- end
14
-
15
- it "returns false when the beginning of a line does not match a Time" do
16
- subject.transform?("NOT A TIME\n2013-01-01 10:00:00").should be_false
17
- end
18
- end