diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | lib/psych.rb | 34 | ||||
-rw-r--r-- | psych.gemspec | 1 | ||||
-rw-r--r-- | test/psych/test_exception.rb | 12 | ||||
-rw-r--r-- | test/psych/test_psych.rb | 46 |
5 files changed, 86 insertions, 11 deletions
@@ -12,8 +12,8 @@ serialize and de-serialize most Ruby objects to and from the YAML format. ## Examples ```ruby -# Load YAML in to a Ruby object -Psych.load('--- foo') # => 'foo' +# Safely load YAML in to a Ruby object +Psych.safe_load('--- foo') # => 'foo' # Emit YAML from a Ruby object Psych.dump("foo") # => "--- foo\n...\n" diff --git a/lib/psych.rb b/lib/psych.rb index fcbb056..c3292d9 100644 --- a/lib/psych.rb +++ b/lib/psych.rb @@ -74,12 +74,15 @@ require 'psych/class_loader' # # ==== Reading from a string # -# Psych.load("--- a") # => 'a' -# Psych.load("---\n - a\n - b") # => ['a', 'b'] +# Psych.safe_load("--- a") # => 'a' +# Psych.safe_load("---\n - a\n - b") # => ['a', 'b'] +# # From a trusted string: +# Psych.load("--- !ruby/range\nbegin: 0\nend: 42\nexcl: false\n") # => 0..42 # # ==== Reading from a file # -# Psych.load_file("database.yml") +# Psych.safe_load_file("data.yml", permitted_classes: [Date]) +# Psych.load_file("trusted_database.yml") # # ==== Exception handling # @@ -548,7 +551,7 @@ module Psych # end # list # => ['foo', 'bar'] # - def self.load_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: [] + def self.load_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: [], **kwargs if legacy_filename != NOT_GIVEN warn_with_uplevel 'Passing filename with the 2nd argument of Psych.load_stream is deprecated. Use keyword argument like Psych.load_stream(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE filename = legacy_filename @@ -556,10 +559,10 @@ module Psych result = if block_given? parse_stream(yaml, filename: filename) do |node| - yield node.to_ruby + yield node.to_ruby(**kwargs) end else - parse_stream(yaml, filename: filename).children.map(&:to_ruby) + parse_stream(yaml, filename: filename).children.map { |node| node.to_ruby(**kwargs) } end return fallback if result.is_a?(Array) && result.empty? @@ -570,9 +573,24 @@ module Psych # Load the document contained in +filename+. Returns the yaml contained in # +filename+ as a Ruby object, or if the file is empty, it returns # the specified +fallback+ return value, which defaults to +false+. - def self.load_file filename, fallback: false + # + # NOTE: This method *should not* be used to parse untrusted documents, such as + # YAML documents that are supplied via user input. Instead, please use the + # safe_load_file method. + def self.load_file filename, **kwargs + File.open(filename, 'r:bom|utf-8') { |f| + self.load f, filename: filename, **kwargs + } + end + + ### + # Safely loads the document contained in +filename+. Returns the yaml contained in + # +filename+ as a Ruby object, or if the file is empty, it returns + # the specified +fallback+ return value, which defaults to +false+. + # See safe_load for options. + def self.safe_load_file filename, **kwargs File.open(filename, 'r:bom|utf-8') { |f| - self.load f, filename: filename, fallback: fallback + self.safe_load f, filename: filename, **kwargs } end diff --git a/psych.gemspec b/psych.gemspec index 5f4bb43..e9e36e6 100644 --- a/psych.gemspec +++ b/psych.gemspec @@ -46,7 +46,6 @@ DESCRIPTION s.extra_rdoc_files = ["README.md"] s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") - s.rubygems_version = "2.5.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if RUBY_ENGINE == 'jruby' diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb index e355c26..78601d0 100644 --- a/test/psych/test_exception.rb +++ b/test/psych/test_exception.rb @@ -118,6 +118,18 @@ module Psych } end + def test_safe_load_file_exception + Tempfile.create(['loadfile', 'yml']) {|t| + t.binmode + t.write '--- `' + t.close + ex = assert_raises(Psych::SyntaxError) do + Psych.safe_load_file t.path + end + assert_equal t.path, ex.file + } + end + def test_psych_parse_takes_file ex = assert_raises(Psych::SyntaxError) do Psych.parse '--- `' diff --git a/test/psych/test_psych.rb b/test/psych/test_psych.rb index 55d9f19..30612de 100644 --- a/test/psych/test_psych.rb +++ b/test/psych/test_psych.rb @@ -125,6 +125,19 @@ class TestPsych < Psych::TestCase assert_equal %w{ foo bar }, docs end + def test_load_stream_freeze + docs = Psych.load_stream("--- foo\n...\n--- bar\n...", freeze: true) + assert_equal %w{ foo bar }, docs + docs.each do |string| + assert_predicate string, :frozen? + end + end + + def test_load_stream_symbolize_names + docs = Psych.load_stream("---\nfoo: bar", symbolize_names: true) + assert_equal [{foo: 'bar'}], docs + end + def test_load_stream_default_fallback assert_equal [], Psych.load_stream("") end @@ -242,6 +255,27 @@ class TestPsych < Psych::TestCase } end + def test_load_file_freeze + Tempfile.create(['yikes', 'yml']) {|t| + t.binmode + t.write('--- hello world') + t.close + + object = Psych.load_file(t.path, freeze: true) + assert_predicate object, :frozen? + } + end + + def test_load_file_symbolize_names + Tempfile.create(['yikes', 'yml']) {|t| + t.binmode + t.write("---\nfoo: bar") + t.close + + assert_equal({foo: 'bar'}, Psych.load_file(t.path, symbolize_names: true)) + } + end + def test_load_file_default_fallback Tempfile.create(['empty', 'yml']) {|t| assert_equal false, Psych.load_file(t.path) @@ -285,6 +319,18 @@ class TestPsych < Psych::TestCase } end + def test_safe_load_file_with_permitted_classe + Tempfile.create(['false', 'yml']) {|t| + t.binmode + t.write("--- !ruby/range\nbegin: 0\nend: 42\nexcl: false\n") + t.close + assert_equal 0..42, Psych.safe_load_file(t.path, permitted_classes: [Range]) + assert_raises(Psych::DisallowedClass) { + Psych.safe_load_file(t.path) + } + } + end + def test_parse_file Tempfile.create(['yikes', 'yml']) {|t| t.binmode |