diff options
Diffstat (limited to 'ext/psych/lib')
-rw-r--r-- | ext/psych/lib/psych.rb | 302 | ||||
-rw-r--r-- | ext/psych/lib/psych/class_loader.rb | 10 | ||||
-rw-r--r-- | ext/psych/lib/psych/core_ext.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/exception.rb | 18 | ||||
-rw-r--r-- | ext/psych/lib/psych/handlers/document_stream.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/handlers/recorder.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/json/stream.rb | 4 | ||||
-rw-r--r-- | ext/psych/lib/psych/json/tree_builder.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/nodes.rb | 14 | ||||
-rw-r--r-- | ext/psych/lib/psych/nodes/node.rb | 8 | ||||
-rw-r--r-- | ext/psych/lib/psych/parser.rb | 13 | ||||
-rw-r--r-- | ext/psych/lib/psych/scalar_scanner.rb | 38 | ||||
-rw-r--r-- | ext/psych/lib/psych/syntax_error.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/tree_builder.rb | 6 | ||||
-rw-r--r-- | ext/psych/lib/psych/versions.rb | 6 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors.rb | 12 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/json_tree.rb | 2 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/to_ruby.rb | 22 | ||||
-rw-r--r-- | ext/psych/lib/psych/visitors/yaml_tree.rb | 96 |
19 files changed, 368 insertions, 193 deletions
diff --git a/ext/psych/lib/psych.rb b/ext/psych/lib/psych.rb index cedf0a4ad6..d87bd9040a 100644 --- a/ext/psych/lib/psych.rb +++ b/ext/psych/lib/psych.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'psych/versions' +require_relative 'psych/versions' case RUBY_ENGINE when 'jruby' - require 'psych_jars' + require_relative 'psych_jars' if JRuby::Util.respond_to?(:load_ext) JRuby::Util.load_ext('org.jruby.ext.psych.PsychLibrary') else @@ -12,28 +12,28 @@ when 'jruby' else require 'psych.so' end -require 'psych/nodes' -require 'psych/streaming' -require 'psych/visitors' -require 'psych/handler' -require 'psych/tree_builder' -require 'psych/parser' -require 'psych/omap' -require 'psych/set' -require 'psych/coder' -require 'psych/core_ext' -require 'psych/stream' -require 'psych/json/tree_builder' -require 'psych/json/stream' -require 'psych/handlers/document_stream' -require 'psych/class_loader' +require_relative 'psych/nodes' +require_relative 'psych/streaming' +require_relative 'psych/visitors' +require_relative 'psych/handler' +require_relative 'psych/tree_builder' +require_relative 'psych/parser' +require_relative 'psych/omap' +require_relative 'psych/set' +require_relative 'psych/coder' +require_relative 'psych/core_ext' +require_relative 'psych/stream' +require_relative 'psych/json/tree_builder' +require_relative 'psych/json/stream' +require_relative 'psych/handlers/document_stream' +require_relative 'psych/class_loader' ### # = Overview # # Psych is a YAML parser and emitter. # Psych leverages libyaml [Home page: https://pyyaml.org/wiki/LibYAML] -# or [HG repo: https://bitbucket.org/xi/libyaml] for its YAML parsing +# or [git repo: https://github.com/yaml/libyaml] for its YAML parsing # and emitting capabilities. In addition to wrapping libyaml, Psych also # knows how to serialize and de-serialize most Ruby objects to and from # the YAML format. @@ -234,9 +234,6 @@ require 'psych/class_loader' module Psych # The version of libyaml Psych is using LIBYAML_VERSION = Psych.libyaml_version.join('.').freeze - # Deprecation guard - NOT_GIVEN = Object.new.freeze - private_constant :NOT_GIVEN ### # Load +yaml+ in to a Ruby data structure. If multiple documents are @@ -249,11 +246,11 @@ module Psych # # Example: # - # Psych.load("--- a") # => 'a' - # Psych.load("---\n - a\n - b") # => ['a', 'b'] + # Psych.unsafe_load("--- a") # => 'a' + # Psych.unsafe_load("---\n - a\n - b") # => ['a', 'b'] # # begin - # Psych.load("--- `", filename: "file.txt") + # Psych.unsafe_load("--- `", filename: "file.txt") # rescue Psych::SyntaxError => ex # ex.file # => 'file.txt' # ex.message # => "(file.txt): found character that cannot start any token" @@ -262,24 +259,19 @@ module Psych # When the optional +symbolize_names+ keyword argument is set to a # true value, returns symbols for keys in Hash objects (default: strings). # - # Psych.load("---\n foo: bar") # => {"foo"=>"bar"} - # Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} + # Psych.unsafe_load("---\n foo: bar") # => {"foo"=>"bar"} + # Psych.unsafe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} # # Raises a TypeError when `yaml` parameter is NilClass # # 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 method. + # load method or the safe_load method. # - def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false, freeze: false - if legacy_filename != NOT_GIVEN - warn_with_uplevel 'Passing filename with the 2nd argument of Psych.load is deprecated. Use keyword argument like Psych.load(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE - filename = legacy_filename - end - + def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false result = parse(yaml, filename: filename) return fallback unless result - result.to_ruby(symbolize_names: symbolize_names, freeze: freeze) + result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer) end ### @@ -289,7 +281,8 @@ module Psych # * TrueClass # * FalseClass # * NilClass - # * Numeric + # * Integer + # * Float # * String # * Array # * Hash @@ -314,7 +307,7 @@ module Psych # A Psych::DisallowedClass exception will be raised if the yaml contains a # class that isn't in the +permitted_classes+ list. # - # A Psych::BadAlias exception will be raised if the yaml contains aliases + # A Psych::AliasesNotEnabled exception will be raised if the yaml contains aliases # but the +aliases+ keyword argument is set to false. # # +filename+ will be used in the exception message if any exception is raised @@ -326,33 +319,13 @@ module Psych # Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"} # Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} # - def self.safe_load yaml, legacy_permitted_classes = NOT_GIVEN, legacy_permitted_symbols = NOT_GIVEN, legacy_aliases = NOT_GIVEN, legacy_filename = NOT_GIVEN, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false - if legacy_permitted_classes != NOT_GIVEN - warn_with_uplevel 'Passing permitted_classes with the 2nd argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, permitted_classes: ...) instead.', uplevel: 1 if $VERBOSE - permitted_classes = legacy_permitted_classes - end - - if legacy_permitted_symbols != NOT_GIVEN - warn_with_uplevel 'Passing permitted_symbols with the 3rd argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, permitted_symbols: ...) instead.', uplevel: 1 if $VERBOSE - permitted_symbols = legacy_permitted_symbols - end - - if legacy_aliases != NOT_GIVEN - warn_with_uplevel 'Passing aliases with the 4th argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, aliases: ...) instead.', uplevel: 1 if $VERBOSE - aliases = legacy_aliases - end - - if legacy_filename != NOT_GIVEN - warn_with_uplevel 'Passing filename with the 5th argument of Psych.safe_load is deprecated. Use keyword argument like Psych.safe_load(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE - filename = legacy_filename - end - + def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false result = parse(yaml, filename: filename) return fallback unless result class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s)) - scanner = ScalarScanner.new class_loader + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer visitor = if aliases Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze else @@ -363,6 +336,47 @@ module Psych end ### + # Load +yaml+ in to a Ruby data structure. If multiple documents are + # provided, the object contained in the first document will be returned. + # +filename+ will be used in the exception message if any exception + # is raised while parsing. If +yaml+ is empty, it returns + # the specified +fallback+ return value, which defaults to +false+. + # + # Raises a Psych::SyntaxError when a YAML syntax error is detected. + # + # Example: + # + # Psych.load("--- a") # => 'a' + # Psych.load("---\n - a\n - b") # => ['a', 'b'] + # + # begin + # Psych.load("--- `", filename: "file.txt") + # rescue Psych::SyntaxError => ex + # ex.file # => 'file.txt' + # ex.message # => "(file.txt): found character that cannot start any token" + # end + # + # When the optional +symbolize_names+ keyword argument is set to a + # true value, returns symbols for keys in Hash objects (default: strings). + # + # Psych.load("---\n foo: bar") # => {"foo"=>"bar"} + # Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"} + # + # Raises a TypeError when `yaml` parameter is NilClass. This method is + # similar to `safe_load` except that `Symbol` objects are allowed by default. + # + def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false + safe_load yaml, permitted_classes: permitted_classes, + permitted_symbols: permitted_symbols, + aliases: aliases, + filename: filename, + fallback: fallback, + symbolize_names: symbolize_names, + freeze: freeze, + strict_integer: strict_integer + end + + ### # Parse a YAML string in +yaml+. Returns the Psych::Nodes::Document. # +filename+ is used in the exception message if a Psych::SyntaxError is # raised. @@ -381,22 +395,12 @@ module Psych # end # # See Psych::Nodes for more information about YAML AST. - def self.parse yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: NOT_GIVEN - if legacy_filename != NOT_GIVEN - warn_with_uplevel 'Passing filename with the 2nd argument of Psych.parse is deprecated. Use keyword argument like Psych.parse(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE - filename = legacy_filename - end - + def self.parse yaml, filename: nil parse_stream(yaml, filename: filename) do |node| return node end - if fallback != NOT_GIVEN - warn_with_uplevel 'Passing the `fallback` keyword argument of Psych.parse is deprecated.', uplevel: 1 if $VERBOSE - fallback - else - false - end + false end ### @@ -445,12 +449,7 @@ module Psych # Raises a TypeError when NilClass is passed. # # See Psych::Nodes for more information about YAML AST. - def self.parse_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, &block - if legacy_filename != NOT_GIVEN - warn_with_uplevel 'Passing filename with the 2nd argument of Psych.parse_stream is deprecated. Use keyword argument like Psych.parse_stream(yaml, filename: ...) instead.', uplevel: 1 if $VERBOSE - filename = legacy_filename - end - + def self.parse_stream yaml, filename: nil, &block if block_given? parser = Psych::Parser.new(Handlers::DocumentStream.new(&block)) parser.parse yaml, filename @@ -490,6 +489,10 @@ module Psych # # Default: <tt>false</tt>. # + # [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string. + # + # Default: <tt>false</tt>. + # # Example: # # # Dump an array, get back a YAML string @@ -503,6 +506,9 @@ module Psych # # # Dump an array to an IO with indentation set # Psych.dump(['a', ['b']], StringIO.new, indentation: 3) + # + # # Dump hash with symbol keys as string + # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n" def self.dump o, io = nil, options = {} if Hash === io options = io @@ -515,6 +521,86 @@ module Psych end ### + # call-seq: + # Psych.safe_dump(o) -> string of yaml + # Psych.safe_dump(o, options) -> string of yaml + # Psych.safe_dump(o, io) -> io object passed in + # Psych.safe_dump(o, io, options) -> io object passed in + # + # Safely dump Ruby object +o+ to a YAML string. Optional +options+ may be passed in + # to control the output format. If an IO object is passed in, the YAML will + # be dumped to that IO object. By default, only the following + # classes are allowed to be serialized: + # + # * TrueClass + # * FalseClass + # * NilClass + # * Integer + # * Float + # * String + # * Array + # * Hash + # + # Arbitrary classes can be allowed by adding those classes to the +permitted_classes+ + # keyword argument. They are additive. For example, to allow Date serialization: + # + # Psych.safe_dump(yaml, permitted_classes: [Date]) + # + # Now the Date class can be dumped in addition to the classes listed above. + # + # A Psych::DisallowedClass exception will be raised if the object contains a + # class that isn't in the +permitted_classes+ list. + # + # Currently supported options are: + # + # [<tt>:indentation</tt>] Number of space characters used to indent. + # Acceptable value should be in <tt>0..9</tt> range, + # otherwise option is ignored. + # + # Default: <tt>2</tt>. + # [<tt>:line_width</tt>] Max character to wrap line at. + # + # Default: <tt>0</tt> (meaning "wrap at 81"). + # [<tt>:canonical</tt>] Write "canonical" YAML form (very verbose, yet + # strictly formal). + # + # Default: <tt>false</tt>. + # [<tt>:header</tt>] Write <tt>%YAML [version]</tt> at the beginning of document. + # + # Default: <tt>false</tt>. + # + # [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string. + # + # Default: <tt>false</tt>. + # + # Example: + # + # # Dump an array, get back a YAML string + # Psych.safe_dump(['a', 'b']) # => "---\n- a\n- b\n" + # + # # Dump an array to an IO object + # Psych.safe_dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890> + # + # # Dump an array with indentation set + # Psych.safe_dump(['a', ['b']], indentation: 3) # => "---\n- a\n- - b\n" + # + # # Dump an array to an IO with indentation set + # Psych.safe_dump(['a', ['b']], StringIO.new, indentation: 3) + # + # # Dump hash with symbol keys as string + # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n" + def self.safe_dump o, io = nil, options = {} + if Hash === io + options = io + io = nil + end + + visitor = Psych::Visitors::RestrictedYAMLTree.create options + visitor << o + visitor.tree.yaml io, options + end + + ### # Dump a list of objects as separate documents to a document stream. # # Example: @@ -551,12 +637,7 @@ module Psych # end # list # => ['foo', 'bar'] # - 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 - end - + def self.load_stream yaml, filename: nil, fallback: [], **kwargs result = if block_given? parse_stream(yaml, filename: filename) do |node| yield node.to_ruby(**kwargs) @@ -577,9 +658,9 @@ module Psych # 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 + def self.unsafe_load_file filename, **kwargs File.open(filename, 'r:bom|utf-8') { |f| - self.load f, filename: filename, **kwargs + self.unsafe_load f, filename: filename, **kwargs } end @@ -594,6 +675,17 @@ module Psych } end + ### + # 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 load for options. + def self.load_file filename, **kwargs + File.open(filename, 'r:bom|utf-8') { |f| + self.load f, filename: filename, **kwargs + } + end + # :stopdoc: def self.add_domain_type domain, type_tag, &block key = ['tag', domain, type_tag].join ':' @@ -616,26 +708,8 @@ module Psych dump_tags[klass] = tag end - # Workaround for emulating `warn '...', uplevel: 1` in Ruby 2.4 or lower. - def self.warn_with_uplevel(message, uplevel: 1) - at = parse_caller(caller[uplevel]).join(':') - warn "#{at}: #{message}" - end - - def self.parse_caller(at) - if /^(.+?):(\d+)(?::in `.*')?/ =~ at - file = $1 - line = $2.to_i - [file, line] - end - end - private_class_method :warn_with_uplevel, :parse_caller - class << self if defined?(Ractor) - require 'forwardable' - extend Forwardable - class Config attr_accessor :load_tags, :dump_tags, :domain_types def initialize @@ -649,7 +723,29 @@ module Psych Ractor.current[:PsychConfig] ||= Config.new end - def_delegators :config, :load_tags, :dump_tags, :domain_types, :load_tags=, :dump_tags=, :domain_types= + def load_tags + config.load_tags + end + + def dump_tags + config.dump_tags + end + + def domain_types + config.domain_types + end + + def load_tags=(value) + config.load_tags = value + end + + def dump_tags=(value) + config.dump_tags = value + end + + def domain_types=(value) + config.domain_types = value + end else attr_accessor :load_tags attr_accessor :dump_tags diff --git a/ext/psych/lib/psych/class_loader.rb b/ext/psych/lib/psych/class_loader.rb index a5d1a7a4a9..50efc35ee2 100644 --- a/ext/psych/lib/psych/class_loader.rb +++ b/ext/psych/lib/psych/class_loader.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'psych/omap' -require 'psych/set' +require_relative 'omap' +require_relative 'set' module Psych class ClassLoader # :nodoc: @@ -35,7 +35,7 @@ module Psych constants.each do |const| konst = const_get const - class_eval <<~RUBY + class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{const.to_s.downcase} load #{konst.inspect} end @@ -86,7 +86,7 @@ module Psych if @symbols.include? sym super else - raise DisallowedClass, 'Symbol' + raise DisallowedClass.new('load', 'Symbol') end end @@ -96,7 +96,7 @@ module Psych if @classes.include? klassname super else - raise DisallowedClass, klassname + raise DisallowedClass.new('load', klassname) end end end diff --git a/ext/psych/lib/psych/core_ext.rb b/ext/psych/lib/psych/core_ext.rb index 81055cc501..0721a133c3 100644 --- a/ext/psych/lib/psych/core_ext.rb +++ b/ext/psych/lib/psych/core_ext.rb @@ -15,5 +15,5 @@ class Object end if defined?(::IRB) - require 'psych/y' + require_relative 'y' end diff --git a/ext/psych/lib/psych/exception.rb b/ext/psych/lib/psych/exception.rb index fac0c42b9f..d7469a4b30 100644 --- a/ext/psych/lib/psych/exception.rb +++ b/ext/psych/lib/psych/exception.rb @@ -6,9 +6,23 @@ module Psych class BadAlias < Exception end + # Subclasses `BadAlias` for backwards compatibility + class AliasesNotEnabled < BadAlias + def initialize + super "Alias parsing was not enabled. To enable it, pass `aliases: true` to `Psych::load` or `Psych::safe_load`." + end + end + + # Subclasses `BadAlias` for backwards compatibility + class AnchorNotDefined < BadAlias + def initialize anchor_name + super "An alias referenced an unknown anchor: #{anchor_name}" + end + end + class DisallowedClass < Exception - def initialize klass_name - super "Tried to load unspecified class: #{klass_name}" + def initialize action, klass_name + super "Tried to #{action} unspecified class: #{klass_name}" end end end diff --git a/ext/psych/lib/psych/handlers/document_stream.rb b/ext/psych/lib/psych/handlers/document_stream.rb index 67da794093..b77115d074 100644 --- a/ext/psych/lib/psych/handlers/document_stream.rb +++ b/ext/psych/lib/psych/handlers/document_stream.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/tree_builder' +require_relative '../tree_builder' module Psych module Handlers diff --git a/ext/psych/lib/psych/handlers/recorder.rb b/ext/psych/lib/psych/handlers/recorder.rb index a8fc7b1144..c98724cb76 100644 --- a/ext/psych/lib/psych/handlers/recorder.rb +++ b/ext/psych/lib/psych/handlers/recorder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/handler' +require_relative '../handler' module Psych module Handlers diff --git a/ext/psych/lib/psych/json/stream.rb b/ext/psych/lib/psych/json/stream.rb index 2ebd3d7a66..24dd4b9baf 100644 --- a/ext/psych/lib/psych/json/stream.rb +++ b/ext/psych/lib/psych/json/stream.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'psych/json/ruby_events' -require 'psych/json/yaml_events' +require_relative 'ruby_events' +require_relative 'yaml_events' module Psych module JSON diff --git a/ext/psych/lib/psych/json/tree_builder.rb b/ext/psych/lib/psych/json/tree_builder.rb index 5c2ee8ca25..9a45f6b94c 100644 --- a/ext/psych/lib/psych/json/tree_builder.rb +++ b/ext/psych/lib/psych/json/tree_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/json/yaml_events' +require_relative 'yaml_events' module Psych module JSON diff --git a/ext/psych/lib/psych/nodes.rb b/ext/psych/lib/psych/nodes.rb index 5842c2e3e5..2fa52e0055 100644 --- a/ext/psych/lib/psych/nodes.rb +++ b/ext/psych/lib/psych/nodes.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'psych/nodes/node' -require 'psych/nodes/stream' -require 'psych/nodes/document' -require 'psych/nodes/sequence' -require 'psych/nodes/scalar' -require 'psych/nodes/mapping' -require 'psych/nodes/alias' +require_relative 'nodes/node' +require_relative 'nodes/stream' +require_relative 'nodes/document' +require_relative 'nodes/sequence' +require_relative 'nodes/scalar' +require_relative 'nodes/mapping' +require_relative 'nodes/alias' module Psych ### diff --git a/ext/psych/lib/psych/nodes/node.rb b/ext/psych/lib/psych/nodes/node.rb index 05cb08dac0..f44fce5f05 100644 --- a/ext/psych/lib/psych/nodes/node.rb +++ b/ext/psych/lib/psych/nodes/node.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'stringio' -require 'psych/class_loader' -require 'psych/scalar_scanner' +require_relative '../class_loader' +require_relative '../scalar_scanner' module Psych module Nodes @@ -46,8 +46,8 @@ module Psych # Convert this node to Ruby. # # See also Psych::Visitors::ToRuby - def to_ruby(symbolize_names: false, freeze: false) - Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze).accept(self) + def to_ruby(symbolize_names: false, freeze: false, strict_integer: false) + Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer).accept(self) end alias :transform :to_ruby diff --git a/ext/psych/lib/psych/parser.rb b/ext/psych/lib/psych/parser.rb index 39bc8289be..2181c730e5 100644 --- a/ext/psych/lib/psych/parser.rb +++ b/ext/psych/lib/psych/parser.rb @@ -48,5 +48,18 @@ module Psych @handler = handler @external_encoding = ANY end + + ### + # call-seq: + # parser.parse(yaml) + # + # Parse the YAML document contained in +yaml+. Events will be called on + # the handler set on the parser instance. + # + # See Psych::Parser and Psych::Parser#handler + + def parse yaml, path = yaml.respond_to?(:path) ? yaml.path : "<unknown>" + _native_parse @handler, yaml, path + end end end diff --git a/ext/psych/lib/psych/scalar_scanner.rb b/ext/psych/lib/psych/scalar_scanner.rb index d565a336e8..3cb4bf3c7e 100644 --- a/ext/psych/lib/psych/scalar_scanner.rb +++ b/ext/psych/lib/psych/scalar_scanner.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'strscan' module Psych ### @@ -9,32 +8,39 @@ module Psych TIME = /^-?\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d\d:\d\d(?:\.\d*)?(?:\s*(?:Z|[-+]\d{1,2}:?(?:\d\d)?))?$/ # Taken from http://yaml.org/type/float.html - FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10) - |[-+]?\.(inf|Inf|INF)(?# infinity) - |\.(nan|NaN|NAN)(?# not a number))$/x + # Base 60, [-+]inf and NaN are handled separately + FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10))$/x # Taken from http://yaml.org/type/int.html - INTEGER = /^(?:[-+]?0b[0-1_,]+ (?# base 2) - |[-+]?0[0-7_,]+ (?# base 8) - |[-+]?(?:0|[1-9][0-9_,]*) (?# base 10) - |[-+]?0x[0-9a-fA-F_,]+ (?# base 16))$/x + INTEGER_STRICT = /^(?:[-+]?0b[0-1_]+ (?# base 2) + |[-+]?0[0-7_]+ (?# base 8) + |[-+]?(0|[1-9][0-9_]*) (?# base 10) + |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x + + # Same as above, but allows commas. + # Not to YML spec, but kept for backwards compatibility + INTEGER_LEGACY = /^(?:[-+]?0b[0-1_,]+ (?# base 2) + |[-+]?0[0-7_,]+ (?# base 8) + |[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*) (?# base 10) + |[-+]?0x[0-9a-fA-F_,]+ (?# base 16))$/x attr_reader :class_loader # Create a new scanner - def initialize class_loader + def initialize class_loader, strict_integer: false @symbol_cache = {} @class_loader = class_loader + @strict_integer = strict_integer end # Tokenize +string+ returning the Ruby object def tokenize string return nil if string.empty? return @symbol_cache[string] if @symbol_cache.key?(string) - + integer_regex = @strict_integer ? INTEGER_STRICT : INTEGER_LEGACY # Check for a String type, being careful not to get caught by hash keys, hex values, and # special floats (e.g., -.inf). - if string.match?(/^[^\d\.:-]?[A-Za-z_\s!@#\$%\^&\*\(\)\{\}\<\>\|\/\\~;=]+/) || string.match?(/\n/) + if string.match?(%r{^[^\d.:-]?[[:alpha:]_\s!@#$%\^&*(){}<>|/\\~;=]+}) || string.match?(/\n/) return string if string.length > 5 if string.match?(/^[^ytonf~]/i) @@ -57,11 +63,11 @@ module Psych elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/) require 'date' begin - class_loader.date.strptime(string, '%Y-%m-%d') + class_loader.date.strptime(string, '%F', Date::GREGORIAN) rescue ArgumentError string end - elsif string.match?(/^\.inf$/i) + elsif string.match?(/^\+?\.inf$/i) Float::INFINITY elsif string.match?(/^-\.inf$/i) -Float::INFINITY @@ -89,9 +95,9 @@ module Psych if string.match?(/\A[-+]?\.\Z/) string else - Float(string.gsub(/[,_]|\.([Ee]|$)/, '\1')) + Float(string.delete(',_').gsub(/\.([Ee]|$)/, '\1')) end - elsif string.match?(INTEGER) + elsif string.match?(integer_regex) parse_int string else string @@ -101,7 +107,7 @@ module Psych ### # Parse and return an int from +string+ def parse_int string - Integer(string.gsub(/[,_]/, '')) + Integer(string.delete(',_')) end ### diff --git a/ext/psych/lib/psych/syntax_error.rb b/ext/psych/lib/psych/syntax_error.rb index 1598e6ff36..a4c9c4a376 100644 --- a/ext/psych/lib/psych/syntax_error.rb +++ b/ext/psych/lib/psych/syntax_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/exception' +require_relative 'exception' module Psych class SyntaxError < Psych::Exception diff --git a/ext/psych/lib/psych/tree_builder.rb b/ext/psych/lib/psych/tree_builder.rb index 47a1695643..83115bd721 100644 --- a/ext/psych/lib/psych/tree_builder.rb +++ b/ext/psych/lib/psych/tree_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/handler' +require_relative 'handler' module Psych ### @@ -41,7 +41,7 @@ module Psych Sequence Mapping }.each do |node| - class_eval %{ + class_eval <<~RUBY, __FILE__, __LINE__ + 1 def start_#{node.downcase}(anchor, tag, implicit, style) n = Nodes::#{node}.new(anchor, tag, implicit, style) set_start_location(n) @@ -54,7 +54,7 @@ module Psych set_end_location(n) n end - } + RUBY end ### diff --git a/ext/psych/lib/psych/versions.rb b/ext/psych/lib/psych/versions.rb index b0ec018b63..b9e8d9ef11 100644 --- a/ext/psych/lib/psych/versions.rb +++ b/ext/psych/lib/psych/versions.rb @@ -1,10 +1,10 @@ - # frozen_string_literal: true + module Psych # The version of Psych you are using - VERSION = '3.3.1' + VERSION = '5.1.2' if RUBY_ENGINE == 'jruby' - DEFAULT_SNAKEYAML_VERSION = '1.28'.freeze + DEFAULT_SNAKEYAML_VERSION = '2.7'.freeze end end diff --git a/ext/psych/lib/psych/visitors.rb b/ext/psych/lib/psych/visitors.rb index e2b084daee..508290d862 100644 --- a/ext/psych/lib/psych/visitors.rb +++ b/ext/psych/lib/psych/visitors.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'psych/visitors/visitor' -require 'psych/visitors/to_ruby' -require 'psych/visitors/emitter' -require 'psych/visitors/yaml_tree' -require 'psych/visitors/json_tree' -require 'psych/visitors/depth_first' +require_relative 'visitors/visitor' +require_relative 'visitors/to_ruby' +require_relative 'visitors/emitter' +require_relative 'visitors/yaml_tree' +require_relative 'visitors/json_tree' +require_relative 'visitors/depth_first' diff --git a/ext/psych/lib/psych/visitors/json_tree.rb b/ext/psych/lib/psych/visitors/json_tree.rb index 9912cb1362..979fc100bd 100644 --- a/ext/psych/lib/psych/visitors/json_tree.rb +++ b/ext/psych/lib/psych/visitors/json_tree.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'psych/json/ruby_events' +require_relative '../json/ruby_events' module Psych module Visitors diff --git a/ext/psych/lib/psych/visitors/to_ruby.rb b/ext/psych/lib/psych/visitors/to_ruby.rb index 4d98850f75..f0fda9bdbc 100644 --- a/ext/psych/lib/psych/visitors/to_ruby.rb +++ b/ext/psych/lib/psych/visitors/to_ruby.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'psych/scalar_scanner' -require 'psych/class_loader' -require 'psych/exception' +require_relative '../scalar_scanner' +require_relative '../class_loader' +require_relative '../exception' unless defined?(Regexp::NOENCODING) Regexp::NOENCODING = 32 @@ -12,9 +12,9 @@ module Psych ### # This class walks a YAML AST, converting each node to Ruby class ToRuby < Psych::Visitors::Visitor - def self.create(symbolize_names: false, freeze: false) + def self.create(symbolize_names: false, freeze: false, strict_integer: false) class_loader = ClassLoader.new - scanner = ScalarScanner.new class_loader + scanner = ScalarScanner.new class_loader, strict_integer: strict_integer new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze) end @@ -80,7 +80,9 @@ module Psych when "!ruby/object:DateTime" class_loader.date_time require 'date' unless defined? DateTime - @ss.parse_time(o.value).to_datetime + t = @ss.parse_time(o.value) + DateTime.civil(*t.to_a[0, 6].reverse, Rational(t.utc_offset, 86400)) + + (t.subsec/86400) when '!ruby/encoding' ::Encoding.find o.value when "!ruby/object:Complex" @@ -99,7 +101,7 @@ module Psych source = $1 options = 0 lang = nil - ($2 || '').split('').each do |option| + $2&.each_char do |option| case option when 'x' then options |= Regexp::EXTENDED when 'i' then options |= Regexp::IGNORECASE @@ -323,7 +325,7 @@ module Psych end def visit_Psych_Nodes_Alias o - @st.fetch(o.anchor) { raise BadAlias, "Unknown alias: #{o.anchor}" } + @st.fetch(o.anchor) { raise AnchorNotDefined, o.anchor } end private @@ -366,7 +368,7 @@ module Psych hash[key] = val end else - if !tagged && @symbolize_names + if !tagged && @symbolize_names && key.is_a?(String) key = key.to_sym elsif !@freeze key = deduplicate(key) @@ -427,7 +429,7 @@ module Psych class NoAliasRuby < ToRuby def visit_Psych_Nodes_Alias o - raise BadAlias, "Unknown alias: #{o.anchor}" + raise AliasesNotEnabled end end end diff --git a/ext/psych/lib/psych/visitors/yaml_tree.rb b/ext/psych/lib/psych/visitors/yaml_tree.rb index ac6777aeb5..a2ebc4d781 100644 --- a/ext/psych/lib/psych/visitors/yaml_tree.rb +++ b/ext/psych/lib/psych/visitors/yaml_tree.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'psych/tree_builder' -require 'psych/scalar_scanner' -require 'psych/class_loader' +require_relative '../tree_builder' +require_relative '../scalar_scanner' +require_relative '../class_loader' module Psych module Visitors @@ -15,30 +15,25 @@ module Psych class YAMLTree < Psych::Visitors::Visitor class Registrar # :nodoc: def initialize - @obj_to_id = {} - @obj_to_node = {} - @targets = [] + @obj_to_id = {}.compare_by_identity + @obj_to_node = {}.compare_by_identity @counter = 0 end def register target, node - return unless target.respond_to? :object_id - @targets << target - @obj_to_node[target.object_id] = node + @obj_to_node[target] = node end def key? target - @obj_to_node.key? target.object_id - rescue NoMethodError - false + @obj_to_node.key? target end def id_for target - @obj_to_id[target.object_id] ||= (@counter += 1) + @obj_to_id[target] ||= (@counter += 1) end def node_for target - @obj_to_node[target.object_id] + @obj_to_node[target] end end @@ -70,6 +65,7 @@ module Psych fail(ArgumentError, "Invalid line_width #{@line_width}, must be non-negative or -1 for unlimited.") end end + @stringify_names = options[:stringify_names] @coders = [] @dispatch_cache = Hash.new do |h,klass| @@ -192,12 +188,13 @@ module Psych register o, @emitter.scalar(o.inspect, nil, '!ruby/regexp', false, false, Nodes::Scalar::ANY) end + def visit_Date o + register o, visit_Integer(o.gregorian) + end + def visit_DateTime o - formatted = if o.offset.zero? - o.strftime("%Y-%m-%d %H:%M:%S.%9N Z".freeze) - else - o.strftime("%Y-%m-%d %H:%M:%S.%9N %:z".freeze) - end + t = o.italy + formatted = format_time t, t.offset.zero? tag = '!ruby/object:DateTime' register o, @emitter.scalar(formatted, nil, tag, false, false, Nodes::Scalar::ANY) end @@ -235,7 +232,6 @@ module Psych end alias :visit_TrueClass :visit_Integer alias :visit_FalseClass :visit_Integer - alias :visit_Date :visit_Integer def visit_Float o if o.nan? @@ -272,6 +268,8 @@ module Psych tag = 'tag:yaml.org,2002:str' plain = false quote = false + elsif o == 'y' || o == 'Y' || o == 'n' || o == 'N' + style = Nodes::Scalar::DOUBLE_QUOTED elsif @line_width && o.length > @line_width style = Nodes::Scalar::FOLDED elsif o =~ /^[^[:word:]][^"]*$/ @@ -326,7 +324,7 @@ module Psych if o.class == ::Hash register(o, @emitter.start_mapping(nil, nil, true, Psych::Nodes::Mapping::BLOCK)) o.each do |k,v| - accept k + accept(@stringify_names && Symbol === k ? k.to_s : k) accept v end @emitter.end_mapping @@ -339,7 +337,7 @@ module Psych register(o, @emitter.start_mapping(nil, '!set', false, Psych::Nodes::Mapping::BLOCK)) o.each do |k,v| - accept k + accept(@stringify_names && Symbol === k ? k.to_s : k) accept v end @@ -480,8 +478,8 @@ module Psych @emitter.end_mapping end - def format_time time - if time.utc? + def format_time time, utc = time.utc? + if utc time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") else time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z") @@ -509,9 +507,9 @@ module Psych def emit_coder c, o case c.type when :scalar - @emitter.scalar c.scalar, nil, c.tag, c.tag.nil?, false, Nodes::Scalar::ANY + @emitter.scalar c.scalar, nil, c.tag, c.tag.nil?, false, c.style when :seq - @emitter.start_sequence nil, c.tag, c.tag.nil?, Nodes::Sequence::BLOCK + @emitter.start_sequence nil, c.tag, c.tag.nil?, c.style c.seq.each do |thing| accept thing end @@ -535,5 +533,51 @@ module Psych end end end + + class RestrictedYAMLTree < YAMLTree + DEFAULT_PERMITTED_CLASSES = { + TrueClass => true, + FalseClass => true, + NilClass => true, + Integer => true, + Float => true, + String => true, + Array => true, + Hash => true, + }.compare_by_identity.freeze + + def initialize emitter, ss, options + super + @permitted_classes = DEFAULT_PERMITTED_CLASSES.dup + Array(options[:permitted_classes]).each do |klass| + @permitted_classes[klass] = true + end + @permitted_symbols = {}.compare_by_identity + Array(options[:permitted_symbols]).each do |symbol| + @permitted_symbols[symbol] = true + end + @aliases = options.fetch(:aliases, false) + end + + def accept target + if !@aliases && @st.key?(target) + raise BadAlias, "Tried to dump an aliased object" + end + + unless Symbol === target || @permitted_classes[target.class] + raise DisallowedClass.new('dump', target.class.name || target.class.inspect) + end + + super + end + + def visit_Symbol sym + unless @permitted_classes[Symbol] || @permitted_symbols[sym] + raise DisallowedClass.new('dump', "Symbol(#{sym.inspect})") + end + + super + end + end end end |