代码之家  ›  专栏  ›  技术社区  ›  Kimmo Lehto

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

  •  2
  • Kimmo Lehto  · 技术社区  · 6 年前

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

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

    ---
    key: value
    ---
    key: !ruby/struct
      foo: bar
    

    加载yaml文件,如使用 山药安全负荷(内容) 将只返回第一个文档:

    { 'key' => 'value' }
    

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

    Psych::DisallowedClass (Tried to load unspecified class: Struct)
    

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

    [
      { 'key' => 'value' },
      { 'key' => #<struct foo="bar"> }
    ]
    

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

    1 回复  |  直到 6 年前
        1
  •  2
  •   Kimmo Lehto    5 年前

    我写了一个利用 YAML.parse_stream 接口:

    编辑 :现在作为宝石 yaml-safe_load_stream . 同时,精神的维护者 YAML 在ruby stdlib)中 adding this feature 去图书馆。

    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
    

    通过这个,您可以做到:

    YAML.safe_load_stream(content, 'file.txt')
    

    并得到一个例外:

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

    从返回的行号 .start_line 与文档开始位置相关,我找不到获取文档开始位置的行号的方法,因此我将文档编号添加到错误消息中。

    它没有类和符号白名单,也没有像 YAML.safe_load .

    还有一些方法可以使用标签,这些标签可能会给出一个带有如此简单的假阳性。 unless tag.nil? 检测。