safe_yaml 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
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