require "yaml" # This needs to be defined up front in case any internal classes need to base # their behavior off of this. module SafeYAML YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck" end require "set" require "safe_yaml/deep" require "safe_yaml/parse/hexadecimal" require "safe_yaml/parse/sexagesimal" require "safe_yaml/parse/date" require "safe_yaml/transform/transformation_map" require "safe_yaml/transform/to_boolean" require "safe_yaml/transform/to_date" require "safe_yaml/transform/to_float" require "safe_yaml/transform/to_integer" require "safe_yaml/transform/to_nil" require "safe_yaml/transform/to_symbol" require "safe_yaml/transform" require "safe_yaml/resolver" require "safe_yaml/syck_hack" if SafeYAML::YAML_ENGINE == "syck" && defined?(JRUBY_VERSION) module SafeYAML MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1 DEFAULT_OPTIONS = Deep.freeze({ :default_mode => nil, :suppress_warnings => false, :deserialize_symbols => false, :whitelisted_tags => [], :custom_initializers => {}, :raise_on_unknown_tag => false }) OPTIONS = Deep.copy(DEFAULT_OPTIONS) module_function def restore_defaults! OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS)) end def tag_safety_check!(tag, options) return if tag.nil? || tag == "!" if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag) raise "Unknown YAML tag '#{tag}'" end end def whitelist!(*classes) classes.each do |klass| whitelist_class!(klass) end end def whitelist_class!(klass) raise "#{klass} not a Class" unless klass.is_a?(::Class) klass_name = klass.name raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty? # Whitelist any built-in YAML tags supplied by Syck or Psych. predefined_tag = predefined_tags[klass] if predefined_tag OPTIONS[:whitelisted_tags] << predefined_tag return end # Exception is exceptional (har har). tag_class = klass < Exception ? "exception" : "object" tag_prefix = case YAML_ENGINE when "psych" then "!ruby/#{tag_class}" when "syck" then "tag:ruby.yaml.org,2002:#{tag_class}" else raise "unknown YAML_ENGINE #{YAML_ENGINE}" end OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}" end def predefined_tags if @predefined_tags.nil? @predefined_tags = {} if YAML_ENGINE == "syck" YAML.tagged_classes.each do |tag, klass| @predefined_tags[klass] = tag end else # Special tags appear to be hard-coded in Psych: # https://github.com/tenderlove/psych/blob/v1.3.4/lib/psych/visitors/to_ruby.rb # Fortunately, there aren't many that SafeYAML doesn't already support. @predefined_tags.merge!({ Exception => "!ruby/exception", Range => "!ruby/range", Regexp => "!ruby/regexp", }) end end @predefined_tags end if YAML_ENGINE == "psych" def tag_is_explicitly_trusted?(tag) false end else TRUSTED_TAGS = Set.new([ "tag:yaml.org,2002:binary", "tag:yaml.org,2002:bool#no", "tag:yaml.org,2002:bool#yes", "tag:yaml.org,2002:float", "tag:yaml.org,2002:float#fix", "tag:yaml.org,2002:int", "tag:yaml.org,2002:map", "tag:yaml.org,2002:null", "tag:yaml.org,2002:seq", "tag:yaml.org,2002:str", "tag:yaml.org,2002:timestamp", "tag:yaml.org,2002:timestamp#ymd" ]).freeze def tag_is_explicitly_trusted?(tag) TRUSTED_TAGS.include?(tag) end end if SafeYAML::YAML_ENGINE == "psych" require "safe_yaml/psych_handler" require "safe_yaml/psych_resolver" require "safe_yaml/safe_to_ruby_visitor" def self.load(yaml, filename=nil, options={}) # If the user hasn't whitelisted any tags, we can go with this implementation which is # significantly faster. if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty? safe_handler = SafeYAML::PsychHandler.new(options) do |result| return result end arguments_for_parse = [yaml] arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD Psych::Parser.new(safe_handler).parse(*arguments_for_parse) return safe_handler.result else safe_resolver = SafeYAML::PsychResolver.new(options) tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ? Psych.parse(yaml, filename) : Psych.parse(yaml) return safe_resolver.resolve_node(tree) end end def self.load_file(filename, options={}) if SafeYAML::MULTI_ARGUMENT_YAML_LOAD File.open(filename, 'r:bom|utf-8') { |f| self.load(f, filename, options) } else # Ruby pukes on 1.9.2 if we try to open an empty file w/ 'r:bom|utf-8'; # so we'll not specify those flags here. This mirrors the behavior for # unsafe_load_file so it's probably preferable anyway. self.load File.open(filename), nil, options end end else require "safe_yaml/syck_resolver" require "safe_yaml/syck_node_monkeypatch" def self.load(yaml, options={}) resolver = SafeYAML::SyckResolver.new(SafeYAML::OPTIONS.merge(options || {})) tree = YAML.parse(yaml) return resolver.resolve_node(tree) end def self.load_file(filename, options={}) File.open(filename) { |f| self.load(f, options) } end end end