如何安全加载包含多个文档的 YAML 文件?

Kim*_*hto 5 ruby yaml

安全加载典型的单个文档 YAML 文件的常规方法是使用YAML.safe_load(content).

YAML 文件可以包含多个文档:

---
key: value
---
key: !ruby/struct
  foo: bar
Run Code Online (Sandbox Code Playgroud)

使用这样加载 YAML 文件YAML.safe_load(content)只会返回第一个文档:

---
key: value
---
key: !ruby/struct
  foo: bar
Run Code Online (Sandbox Code Playgroud)

如果您拆分文件并尝试安全加载第二个文档,则会按预期收到异常:

{ 'key' => 'value' }
Run Code Online (Sandbox Code Playgroud)

要加载多个文档,您可以使用YAML.load_stream(content)它返回一个数组:

Psych::DisallowedClass (Tried to load unspecified class: Struct)
Run Code Online (Sandbox Code Playgroud)

问题是没有YAML.safe_load_stream会引发非白名单数据类型的异常。

Kim*_*hto 3

我编写了一个利用该YAML.parse_stream接口的解决方法:

编辑:现在作为 gem yaml-safe_load_stream。此外,Psych(ruby stdlib 中的)的维护者YAML正在考虑将此功能添加到库中。

require 'yaml'

module YAML
  def safe_load_stream(yaml, filename = nil, &block)
    parse_stream(yaml, filename) do |stream|
      raise_if_tags(stream, filename)
      if block_given?
        yield stream.to_ruby
      else
        stream.to_ruby
      end
    end
  end
  module_function :safe_load_stream

  def raise_if_tags(obj, filename = nil, doc_num = 1)
    doc_num += 1 if obj.is_a?(Psych::Nodes::Document)

    if obj.respond_to?(:tag)
      if tag = obj.tag
        message = "tag #{tag} encountered on line #{obj.start_line} column #{obj.start_column} of document #{doc_num}"
        message << " in file #{filename}" if filename
        raise Psych::DisallowedClass, message
      end
    end

    if obj.respond_to?(:children)
      Array(obj.children).each do |child|
        raise_if_tags(child, filename, doc_num)
      end
    end
  end
  module_function :raise_if_tags
  private_class_method :raise_if_tags
end
Run Code Online (Sandbox Code Playgroud)

有了这个你可以这样做:

YAML.safe_load_stream(content, 'file.txt')
Run Code Online (Sandbox Code Playgroud)

并得到一个例外:

Psych::DisallowedClass (Tried to load unspecified class: tag !ruby/struct
encountered on line 1 column 7 of document 2 in file file.txt)
Run Code Online (Sandbox Code Playgroud)

返回的行号.start_line是相对于文档开始的,我没有找到获取文档开始的行号的方法,所以我将文档号添加到错误消息中。

它没有像YAML.safe_load.

还有一些使用标签的方法可能会通过如此简单的检测给出误报unless tag.nil?