[前][次][番号順一覧][スレッド一覧]

ruby-changes:28698

From: tenderlove <ko1@a...>
Date: Wed, 15 May 2013 02:27:02 +0900 (JST)
Subject: [ruby-changes:28698] tenderlove:r40750 (trunk): * ext/psych/lib/psych.rb: Adding Psych.safe_load for loading a user

tenderlove	2013-05-15 02:26:41 +0900 (Wed, 15 May 2013)

  New Revision: 40750

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=40750

  Log:
    * ext/psych/lib/psych.rb: Adding Psych.safe_load for loading a user
      defined, restricted subset of Ruby object types.
    * ext/psych/lib/psych/class_loader.rb: A class loader for
      encapsulating the logic for which objects are allowed to be
      deserialized.
    * ext/psych/lib/psych/deprecated.rb: Changes to use the class loader
    * ext/psych/lib/psych/exception.rb: ditto
    * ext/psych/lib/psych/json/stream.rb: ditto
    * ext/psych/lib/psych/nodes/node.rb: ditto
    * ext/psych/lib/psych/scalar_scanner.rb: ditto
    * ext/psych/lib/psych/stream.rb: ditto
    * ext/psych/lib/psych/streaming.rb: ditto
    * ext/psych/lib/psych/visitors/json_tree.rb: ditto
    * ext/psych/lib/psych/visitors/to_ruby.rb: ditto
    * ext/psych/lib/psych/visitors/yaml_tree.rb: ditto
    * ext/psych/psych_to_ruby.c: ditto
    * test/psych/helper.rb: ditto
    * test/psych/test_safe_load.rb: tests for restricted subset.
    * test/psych/test_scalar_scanner.rb: ditto
    * test/psych/visitors/test_to_ruby.rb: ditto
    * test/psych/visitors/test_yaml_tree.rb: ditto

  Added files:
    trunk/ext/psych/lib/psych/class_loader.rb
    trunk/test/psych/test_safe_load.rb
  Modified files:
    trunk/ChangeLog
    trunk/ext/psych/lib/psych/deprecated.rb
    trunk/ext/psych/lib/psych/exception.rb
    trunk/ext/psych/lib/psych/json/stream.rb
    trunk/ext/psych/lib/psych/nodes/node.rb
    trunk/ext/psych/lib/psych/scalar_scanner.rb
    trunk/ext/psych/lib/psych/stream.rb
    trunk/ext/psych/lib/psych/streaming.rb
    trunk/ext/psych/lib/psych/visitors/json_tree.rb
    trunk/ext/psych/lib/psych/visitors/to_ruby.rb
    trunk/ext/psych/lib/psych/visitors/yaml_tree.rb
    trunk/ext/psych/lib/psych.rb
    trunk/ext/psych/psych_to_ruby.c
    trunk/test/psych/helper.rb
    trunk/test/psych/test_scalar_scanner.rb
    trunk/test/psych/visitors/test_to_ruby.rb
    trunk/test/psych/visitors/test_yaml_tree.rb

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 40749)
+++ ChangeLog	(revision 40750)
@@ -1,3 +1,27 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Wed May 15 02:22:16 2013  Aaron Patterson <aaron@t...>
+
+	* ext/psych/lib/psych.rb: Adding Psych.safe_load for loading a user
+	  defined, restricted subset of Ruby object types.
+	* ext/psych/lib/psych/class_loader.rb: A class loader for
+	  encapsulating the logic for which objects are allowed to be
+	  deserialized.
+	* ext/psych/lib/psych/deprecated.rb: Changes to use the class loader
+	* ext/psych/lib/psych/exception.rb: ditto
+	* ext/psych/lib/psych/json/stream.rb: ditto
+	* ext/psych/lib/psych/nodes/node.rb: ditto
+	* ext/psych/lib/psych/scalar_scanner.rb: ditto
+	* ext/psych/lib/psych/stream.rb: ditto
+	* ext/psych/lib/psych/streaming.rb: ditto
+	* ext/psych/lib/psych/visitors/json_tree.rb: ditto
+	* ext/psych/lib/psych/visitors/to_ruby.rb: ditto
+	* ext/psych/lib/psych/visitors/yaml_tree.rb: ditto
+	* ext/psych/psych_to_ruby.c: ditto
+	* test/psych/helper.rb: ditto
+	* test/psych/test_safe_load.rb: tests for restricted subset.
+	* test/psych/test_scalar_scanner.rb: ditto
+	* test/psych/visitors/test_to_ruby.rb: ditto
+	* test/psych/visitors/test_yaml_tree.rb: ditto
+
 Wed May 15 02:06:35 2013  Aaron Patterson <aaron@t...>
 
 	* test/psych/helper.rb: envutil is not available outside Ruby, so
Index: ext/psych/lib/psych/scalar_scanner.rb
===================================================================
--- ext/psych/lib/psych/scalar_scanner.rb	(revision 40749)
+++ ext/psych/lib/psych/scalar_scanner.rb	(revision 40750)
@@ -19,10 +19,13 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L19
                   |[-+]?(?:0|[1-9][0-9_]*) (?# base 10)
                   |[-+]?0x[0-9a-fA-F_]+    (?# base 16))$/x
 
+    attr_reader :class_loader
+
     # Create a new scanner
-    def initialize
+    def initialize class_loader
       @string_cache = {}
       @symbol_cache = {}
+      @class_loader = class_loader
     end
 
     # Tokenize +string+ returning the ruby object
@@ -63,7 +66,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L66
       when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
         require 'date'
         begin
-          Date.strptime(string, '%Y-%m-%d')
+          class_loader.date.strptime(string, '%Y-%m-%d')
         rescue ArgumentError
           string
         end
@@ -75,9 +78,9 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L78
         Float::NAN
       when /^:./
         if string =~ /^:(["'])(.*)\1/
-          @symbol_cache[string] = $2.sub(/^:/, '').to_sym
+          @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, ''))
         else
-          @symbol_cache[string] = string.sub(/^:/, '').to_sym
+          @symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, ''))
         end
       when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+$/
         i = 0
@@ -117,6 +120,8 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L120
     ###
     # Parse and return a Time from +string+
     def parse_time string
+      klass = class_loader.load 'Time'
+
       date, time = *(string.split(/[ tT]/, 2))
       (yy, m, dd) = date.split('-').map { |x| x.to_i }
       md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/)
@@ -124,10 +129,10 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L129
       (hh, mm, ss) = md[1].split(':').map { |x| x.to_i }
       us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000
 
-      time = Time.utc(yy, m, dd, hh, mm, ss, us)
+      time = klass.utc(yy, m, dd, hh, mm, ss, us)
 
       return time if 'Z' == md[3]
-      return Time.at(time.to_i, us) unless md[3]
+      return klass.at(time.to_i, us) unless md[3]
 
       tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) }
       offset = tz.first * 3600
@@ -138,7 +143,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/scalar_scanner.rb#L143
         offset += ((tz[1] || 0) * 60)
       end
 
-      Time.at((time - offset).to_i, us)
+      klass.at((time - offset).to_i, us)
     end
   end
 end
Index: ext/psych/lib/psych/visitors/yaml_tree.rb
===================================================================
--- ext/psych/lib/psych/visitors/yaml_tree.rb	(revision 40749)
+++ ext/psych/lib/psych/visitors/yaml_tree.rb	(revision 40750)
@@ -1,3 +1,7 @@ https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/yaml_tree.rb#L1
+require 'psych/tree_builder'
+require 'psych/scalar_scanner'
+require 'psych/class_loader'
+
 module Psych
   module Visitors
     ###
@@ -36,7 +40,14 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/yaml_tree.rb#L40
       alias :finished? :finished
       alias :started? :started
 
-      def initialize options = {}, emitter = TreeBuilder.new, ss = ScalarScanner.new
+      def self.create options = {}, emitter = nil
+        emitter      ||= TreeBuilder.new
+        class_loader = ClassLoader.new
+        ss           = ScalarScanner.new class_loader
+        new(emitter, ss, options)
+      end
+
+      def initialize emitter, ss, options
         super()
         @started  = false
         @finished = false
Index: ext/psych/lib/psych/visitors/to_ruby.rb
===================================================================
--- ext/psych/lib/psych/visitors/to_ruby.rb	(revision 40749)
+++ ext/psych/lib/psych/visitors/to_ruby.rb	(revision 40750)
@@ -1,4 +1,5 @@ https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L1
 require 'psych/scalar_scanner'
+require 'psych/class_loader'
 require 'psych/exception'
 
 unless defined?(Regexp::NOENCODING)
@@ -10,11 +11,20 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L11
     ###
     # This class walks a YAML AST, converting each node to ruby
     class ToRuby < Psych::Visitors::Visitor
-      def initialize ss = ScalarScanner.new
+      def self.create
+        class_loader = ClassLoader.new
+        scanner      = ScalarScanner.new class_loader
+        new(scanner, class_loader)
+      end
+
+      attr_reader :class_loader
+
+      def initialize ss, class_loader
         super()
         @st = {}
         @ss = ss
         @domain_types = Psych.domain_types
+        @class_loader = class_loader
       end
 
       def accept target
@@ -33,7 +43,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L43
       end
 
       def deserialize o
-        if klass = Psych.load_tags[o.tag]
+        if klass = resolve_class(Psych.load_tags[o.tag])
           instance = klass.allocate
 
           if instance.respond_to?(:init_with)
@@ -60,19 +70,23 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L70
           end
         when '!ruby/object:BigDecimal'
           require 'bigdecimal'
-          BigDecimal._load o.value
+          class_loader.big_decimal._load o.value
         when "!ruby/object:DateTime"
+          class_loader.date_time
           require 'date'
           @ss.parse_time(o.value).to_datetime
         when "!ruby/object:Complex"
+          class_loader.complex
           Complex(o.value)
         when "!ruby/object:Rational"
+          class_loader.rational
           Rational(o.value)
         when "!ruby/class", "!ruby/module"
           resolve_class o.value
         when "tag:yaml.org,2002:float", "!float"
           Float(@ss.tokenize(o.value))
         when "!ruby/regexp"
+          klass = class_loader.regexp
           o.value =~ /^\/(.*)\/([mixn]*)$/
           source  = $1
           options = 0
@@ -86,15 +100,16 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L100
             else lang = option
             end
           end
-          Regexp.new(*[source, options, lang].compact)
+          klass.new(*[source, options, lang].compact)
         when "!ruby/range"
+          klass = class_loader.range
           args = o.value.split(/([.]{2,3})/, 2).map { |s|
             accept Nodes::Scalar.new(s)
           }
           args.push(args.delete_at(1) == '...')
-          Range.new(*args)
+          klass.new(*args)
         when /^!ruby\/sym(bol)?:?(.*)?$/
-          o.value.to_sym
+          class_loader.symbolize o.value
         else
           @ss.tokenize o.value
         end
@@ -106,7 +121,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L121
       end
 
       def visit_Psych_Nodes_Sequence o
-        if klass = Psych.load_tags[o.tag]
+        if klass = resolve_class(Psych.load_tags[o.tag])
           instance = klass.allocate
 
           if instance.respond_to?(:init_with)
@@ -138,22 +153,24 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L153
       end
 
       def visit_Psych_Nodes_Mapping o
-        return revive(Psych.load_tags[o.tag], o) if Psych.load_tags[o.tag]
+        if Psych.load_tags[o.tag]
+          return revive(resolve_class(Psych.load_tags[o.tag]), o)
+        end
         return revive_hash({}, o) unless o.tag
 
         case o.tag
         when /^!ruby\/struct:?(.*)?$/
-          klass = resolve_class($1)
+          klass = resolve_class($1) if $1
 
           if klass
             s = register(o, klass.allocate)
 
             members = {}
-            struct_members = s.members.map { |x| x.to_sym }
+            struct_members = s.members.map { |x| class_loader.symbolize x }
             o.children.each_slice(2) do |k,v|
               member = accept(k)
               value  = accept(v)
-              if struct_members.include?(member.to_sym)
+              if struct_members.include?(class_loader.symbolize(member))
                 s.send("#{member}=", value)
               else
                 members[member.to_s.sub(/^@/, '')] = value
@@ -161,22 +178,27 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L178
             end
             init_with(s, members, o)
           else
+            klass = class_loader.struct
             members = o.children.map { |c| accept c }
             h = Hash[*members]
-            Struct.new(*h.map { |k,v| k.to_sym }).new(*h.map { |k,v| v })
+            klass.new(*h.map { |k,v|
+              class_loader.symbolize k
+            }).new(*h.map { |k,v| v })
           end
 
         when /^!ruby\/object:?(.*)?$/
           name = $1 || 'Object'
 
           if name == 'Complex'
+            class_loader.complex
             h = Hash[*o.children.map { |c| accept c }]
             register o, Complex(h['real'], h['image'])
           elsif name == 'Rational'
+            class_loader.rational
             h = Hash[*o.children.map { |c| accept c }]
             register o, Rational(h['numerator'], h['denominator'])
           else
-            obj = revive((resolve_class(name) || Object), o)
+            obj = revive((resolve_class(name) || class_loader.object), o)
             obj
           end
 
@@ -214,18 +236,19 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L236
           list
 
         when '!ruby/range'
+          klass = class_loader.range
           h = Hash[*o.children.map { |c| accept c }]
-          register o, Range.new(h['begin'], h['end'], h['excl'])
+          register o, klass.new(h['begin'], h['end'], h['excl'])
 
         when /^!ruby\/exception:?(.*)?$/
           h = Hash[*o.children.map { |c| accept c }]
 
-          e = build_exception((resolve_class($1) || Exception),
+          e = build_exception((resolve_class($1) || class_loader.exception),
                               h.delete('message'))
           init_with(e, h, o)
 
         when '!set', 'tag:yaml.org,2002:set'
-          set = Psych::Set.new
+          set = class_loader.psych_set.new
           @st[o.anchor] = set if o.anchor
           o.children.each_slice(2) do |k,v|
             set[accept(k)] = accept(v)
@@ -236,7 +259,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L259
           revive_hash resolve_class($1).new, o
 
         when '!omap', 'tag:yaml.org,2002:omap'
-          map = register(o, Psych::Omap.new)
+          map = register(o, class_loader.psych_omap.new)
           o.children.each_slice(2) do |l,r|
             map[accept(l)] = accept r
           end
@@ -336,21 +359,13 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/to_ruby.rb#L359
 
       # Convert +klassname+ to a Class
       def resolve_class klassname
-        return nil unless klassname and not klassname.empty?
-
-        name    = klassname
-        retried = false
+        class_loader.load klassname
+      end
+    end
 
-        begin
-          path2class(name)
-        rescue ArgumentError, NameError => ex
-          unless retried
-            name    = "Struct::#{name}"
-            retried = ex
-            retry
-          end
-          raise retried
-        end
+    class NoAliasRuby < ToRuby
+      def visit_Psych_Nodes_Alias o
+        raise BadAlias, "Unknown alias: #{o.anchor}"
       end
     end
   end
Index: ext/psych/lib/psych/visitors/json_tree.rb
===================================================================
--- ext/psych/lib/psych/visitors/json_tree.rb	(revision 40749)
+++ ext/psych/lib/psych/visitors/json_tree.rb	(revision 40750)
@@ -5,8 +5,11 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/visitors/json_tree.rb#L5
     class JSONTree < YAMLTree
       include Psych::JSON::RubyEvents
 
-      def initialize options = {}, emitter = Psych::JSON::TreeBuilder.new
-        super
+      def self.create options = {}
+        emitter = Psych::JSON::TreeBuilder.new
+        class_loader = ClassLoader.new
+        ss           = ScalarScanner.new class_loader
+        new(emitter, ss, options)
       end
 
       def accept target
Index: ext/psych/lib/psych/streaming.rb
===================================================================
--- ext/psych/lib/psych/streaming.rb	(revision 40749)
+++ ext/psych/lib/psych/streaming.rb	(revision 40750)
@@ -1,10 +1,15 @@ https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/streaming.rb#L1
 module Psych
   module Streaming
-    ###
-    # Create a new streaming emitter.  Emitter will print to +io+.  See
-    # Psych::Stream for an example.
-    def initialize io
-      super({}, self.class.const_get(:Emitter).new(io))
+    module ClassMethods
+      ###
+      # Create a new streaming emitter.  Emitter will print to +io+.  See
+      # Psych::Stream for an example.
+      def new io
+        emitter      = const_get(:Emitter).new(io)
+        class_loader = ClassLoader.new
+        ss           = ScalarScanner.new class_loader
+        super(emitter, ss, {})
+      end
     end
 
     ###
Index: ext/psych/lib/psych/class_loader.rb
===================================================================
--- ext/psych/lib/psych/class_loader.rb	(revision 0)
+++ ext/psych/lib/psych/class_loader.rb	(revision 40750)
@@ -0,0 +1,101 @@ https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/class_loader.rb#L1
+require 'psych/omap'
+require 'psych/set'
+
+module Psych
+  class ClassLoader # :nodoc:
+    BIG_DECIMAL = 'BigDecimal'
+    COMPLEX     = 'Complex'
+    DATE        = 'Date'
+    DATE_TIME   = 'DateTime'
+    EXCEPTION   = 'Exception'
+    OBJECT      = 'Object'
+    PSYCH_OMAP  = 'Psych::Omap'
+    PSYCH_SET   = 'Psych::Set'
+    RANGE       = 'Range'
+    RATIONAL    = 'Rational'
+    REGEXP      = 'Regexp'
+    STRUCT      = 'Struct'
+    SYMBOL      = 'Symbol'
+
+    def initialize
+      @cache = CACHE.dup
+    end
+
+    def load klassname
+      return nil if !klassname || klassname.empty?
+
+      find klassname
+    end
+
+    def symbolize sym
+      symbol
+      sym.to_sym
+    end
+
+    constants.each do |const|
+      konst = const_get const
+      define_method(const.to_s.downcase) do
+        load konst
+      end
+    end
+
+    private
+
+    def find klassname
+      @cache[klassname] ||= resolve(klassname)
+    end
+
+    def resolve klassname
+      name    = klassname
+      retried = false
+
+      begin
+        path2class(name)
+      rescue ArgumentError, NameError => ex
+        unless retried
+          name    = "Struct::#{name}"
+          retried = ex
+          retry
+        end
+        raise retried
+      end
+    end
+
+    CACHE = Hash[constants.map { |const|
+      val = const_get const
+      begin
+        [val, ::Object.const_get(val)]
+      rescue
+        nil
+      end
+    }.compact]
+
+    class Restricted < ClassLoader
+      def initialize classes, symbols
+        @classes = classes
+        @symbols = symbols
+        super()
+      end
+
+      def symbolize sym
+        return super if @symbols.empty?
+
+        if @symbols.include? sym
+          super
+        else
+          raise DisallowedClass, 'Symbol'
+        end
+      end
+
+      private
+
+      def find klassname
+        if @classes.include? klassname
+          super
+        else
+          raise DisallowedClass, klassname
+        end
+      end
+    end
+  end
+end
Index: ext/psych/lib/psych/stream.rb
===================================================================
--- ext/psych/lib/psych/stream.rb	(revision 40749)
+++ ext/psych/lib/psych/stream.rb	(revision 40750)
@@ -32,5 +32,6 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/stream.rb#L32
     end
 
     include Psych::Streaming
+    extend Psych::Streaming::ClassMethods
   end
 end
Index: ext/psych/lib/psych/exception.rb
===================================================================
--- ext/psych/lib/psych/exception.rb	(revision 40749)
+++ ext/psych/lib/psych/exception.rb	(revision 40750)
@@ -4,4 +4,10 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/exception.rb#L4
 
   class BadAlias < Exception
   end
+
+  class DisallowedClass < Exception
+    def initialize klass_name
+      super "Tried to load unspecified class: #{klass_name}"
+    end
+  end
 end
Index: ext/psych/lib/psych/deprecated.rb
===================================================================
--- ext/psych/lib/psych/deprecated.rb	(revision 40749)
+++ ext/psych/lib/psych/deprecated.rb	(revision 40750)
@@ -35,7 +35,8 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/deprecated.rb#L35
     warn "#{caller[0]}: detect_implicit is deprecated" if $VERBOSE
     return '' unless String === thing
     return 'null' if '' == thing
-    ScalarScanner.new.tokenize(thing).class.name.downcase
+    ss = ScalarScanner.new(ClassLoader.new)
+    ss.tokenize(thing).class.name.downcase
   end
 
   def self.add_ruby_type type_tag, &block
Index: ext/psych/lib/psych/nodes/node.rb
===================================================================
--- ext/psych/lib/psych/nodes/node.rb	(revision 40749)
+++ ext/psych/lib/psych/nodes/node.rb	(revision 40750)
@@ -1,4 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/nodes/node.rb#L1
 require 'stringio'
+require 'psych/class_loader'
+require 'psych/scalar_scanner'
 
 module Psych
   module Nodes
@@ -32,7 +34,7 @@ module Psych https://github.com/ruby/ruby/blob/trunk/ext/psych/lib/psych/nodes/node.rb#L34
       #
       # See also Psych::Visitors::ToRuby
       def to_ruby
-        Visitors::ToRuby.new.accept self
+        Visitors::ToRuby.create.accept(self)
       end
       alias :transform :to_ruby
 
Index: ext/psych/lib/psych/json/stream (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]