psych 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  #endif
4
4
 
5
5
  #if HAVE_CONFIG_H
6
- #include <config.h>
6
+ #include "config.h"
7
7
  #endif
8
8
 
9
9
  #include <yaml.h>
@@ -175,14 +175,14 @@ yaml_string_join(
175
175
  * Check the octet at the specified position.
176
176
  */
177
177
 
178
- #define CHECK_AT(string,octet,offset) \
178
+ #define CHECK_AT(string,octet,offset) \
179
179
  ((string).pointer[offset] == (yaml_char_t)(octet))
180
180
 
181
181
  /*
182
182
  * Check the current octet in the buffer.
183
183
  */
184
184
 
185
- #define CHECK(string,octet) CHECK_AT((string),(octet),0)
185
+ #define CHECK(string,octet) (CHECK_AT((string),(octet),0))
186
186
 
187
187
  /*
188
188
  * Check if the character at the specified position is an alphabetical
@@ -10,11 +10,7 @@ when 'jruby'
10
10
  org.jruby.ext.psych.PsychLibrary.new.load(JRuby.runtime, false)
11
11
  end
12
12
  else
13
- begin
14
- require "#{RUBY_VERSION[/\d+\.\d+/]}/psych.so"
15
- rescue LoadError
16
- require 'psych.so'
17
- end
13
+ require 'psych.so'
18
14
  end
19
15
  require 'psych/nodes'
20
16
  require 'psych/streaming'
@@ -268,7 +264,11 @@ module Psych
268
264
  #
269
265
  # Raises a TypeError when `yaml` parameter is NilClass
270
266
  #
271
- def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false
267
+ # NOTE: This method *should not* be used to parse untrusted documents, such as
268
+ # YAML documents that are supplied via user input. Instead, please use the
269
+ # safe_load method.
270
+ #
271
+ def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false, freeze: false
272
272
  if legacy_filename != NOT_GIVEN
273
273
  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
274
274
  filename = legacy_filename
@@ -276,8 +276,7 @@ module Psych
276
276
 
277
277
  result = parse(yaml, filename: filename)
278
278
  return fallback unless result
279
- result = result.to_ruby if result
280
- symbolize_names!(result) if symbolize_names
279
+ result = result.to_ruby(symbolize_names: symbolize_names, freeze: freeze) if result
281
280
  result
282
281
  end
283
282
 
@@ -325,7 +324,7 @@ module Psych
325
324
  # Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"}
326
325
  # Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
327
326
  #
328
- 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
327
+ 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
329
328
  if legacy_permitted_classes != NOT_GIVEN
330
329
  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
331
330
  permitted_classes = legacy_permitted_classes
@@ -353,12 +352,11 @@ module Psych
353
352
  permitted_symbols.map(&:to_s))
354
353
  scanner = ScalarScanner.new class_loader
355
354
  visitor = if aliases
356
- Visitors::ToRuby.new scanner, class_loader
355
+ Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
357
356
  else
358
- Visitors::NoAliasRuby.new scanner, class_loader
357
+ Visitors::NoAliasRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
359
358
  end
360
359
  result = visitor.accept result
361
- symbolize_names!(result) if symbolize_names
362
360
  result
363
361
  end
364
362
 
@@ -604,19 +602,6 @@ module Psych
604
602
  @dump_tags[klass] = tag
605
603
  end
606
604
 
607
- def self.symbolize_names!(result)
608
- case result
609
- when Hash
610
- result.keys.each do |key|
611
- result[key.to_sym] = symbolize_names!(result.delete(key))
612
- end
613
- when Array
614
- result.map! { |r| symbolize_names!(r) }
615
- end
616
- result
617
- end
618
- private_class_method :symbolize_names!
619
-
620
605
  # Workaround for emulating `warn '...', uplevel: 1` in Ruby 2.4 or lower.
621
606
  def self.warn_with_uplevel(message, uplevel: 1)
622
607
  at = parse_caller(caller[uplevel]).join(':')
@@ -46,8 +46,8 @@ module Psych
46
46
  # Convert this node to Ruby.
47
47
  #
48
48
  # See also Psych::Visitors::ToRuby
49
- def to_ruby
50
- Visitors::ToRuby.create.accept(self)
49
+ def to_ruby(symbolize_names: false, freeze: false)
50
+ Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze).accept(self)
51
51
  end
52
52
  alias :transform :to_ruby
53
53
 
@@ -14,16 +14,15 @@ module Psych
14
14
  |\.(nan|NaN|NAN)(?# not a number))$/x
15
15
 
16
16
  # Taken from http://yaml.org/type/int.html
17
- INTEGER = /^(?:[-+]?0b[0-1_]+ (?# base 2)
18
- |[-+]?0[0-7_]+ (?# base 8)
19
- |[-+]?(?:0|[1-9][0-9_]*) (?# base 10)
20
- |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x
17
+ INTEGER = /^(?:[-+]?0b[0-1_,]+ (?# base 2)
18
+ |[-+]?0[0-7_,]+ (?# base 8)
19
+ |[-+]?(?:0|[1-9][0-9_,]*) (?# base 10)
20
+ |[-+]?0x[0-9a-fA-F_,]+ (?# base 16))$/x
21
21
 
22
22
  attr_reader :class_loader
23
23
 
24
24
  # Create a new scanner
25
25
  def initialize class_loader
26
- @string_cache = {}
27
26
  @symbol_cache = {}
28
27
  @class_loader = class_loader
29
28
  end
@@ -31,81 +30,70 @@ module Psych
31
30
  # Tokenize +string+ returning the Ruby object
32
31
  def tokenize string
33
32
  return nil if string.empty?
34
- return string if @string_cache.key?(string)
35
33
  return @symbol_cache[string] if @symbol_cache.key?(string)
36
34
 
37
- case string
38
35
  # Check for a String type, being careful not to get caught by hash keys, hex values, and
39
36
  # special floats (e.g., -.inf).
40
- when /^[^\d\.:-]?[A-Za-z_\s!@#\$%\^&\*\(\)\{\}\<\>\|\/\\~;=]+/, /\n/
41
- if string.length > 5
42
- @string_cache[string] = true
43
- return string
44
- end
37
+ if string.match?(/^[^\d\.:-]?[A-Za-z_\s!@#\$%\^&\*\(\)\{\}\<\>\|\/\\~;=]+/) || string.match?(/\n/)
38
+ return string if string.length > 5
45
39
 
46
- case string
47
- when /^[^ytonf~]/i
48
- @string_cache[string] = true
40
+ if string.match?(/^[^ytonf~]/i)
49
41
  string
50
- when '~', /^null$/i
42
+ elsif string == '~' || string.match?(/^null$/i)
51
43
  nil
52
- when /^(yes|true|on)$/i
44
+ elsif string.match?(/^(yes|true|on)$/i)
53
45
  true
54
- when /^(no|false|off)$/i
46
+ elsif string.match?(/^(no|false|off)$/i)
55
47
  false
56
48
  else
57
- @string_cache[string] = true
58
49
  string
59
50
  end
60
- when TIME
51
+ elsif string.match?(TIME)
61
52
  begin
62
53
  parse_time string
63
54
  rescue ArgumentError
64
55
  string
65
56
  end
66
- when /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/
57
+ elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/)
67
58
  require 'date'
68
59
  begin
69
60
  class_loader.date.strptime(string, '%Y-%m-%d')
70
61
  rescue ArgumentError
71
62
  string
72
63
  end
73
- when /^\.inf$/i
64
+ elsif string.match?(/^\.inf$/i)
74
65
  Float::INFINITY
75
- when /^-\.inf$/i
66
+ elsif string.match?(/^-\.inf$/i)
76
67
  -Float::INFINITY
77
- when /^\.nan$/i
68
+ elsif string.match?(/^\.nan$/i)
78
69
  Float::NAN
79
- when /^:./
70
+ elsif string.match?(/^:./)
80
71
  if string =~ /^:(["'])(.*)\1/
81
72
  @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, ''))
82
73
  else
83
74
  @symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, ''))
84
75
  end
85
- when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/
76
+ elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/)
86
77
  i = 0
87
78
  string.split(':').each_with_index do |n,e|
88
79
  i += (n.to_i * 60 ** (e - 2).abs)
89
80
  end
90
81
  i
91
- when /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/
82
+ elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/)
92
83
  i = 0
93
84
  string.split(':').each_with_index do |n,e|
94
85
  i += (n.to_f * 60 ** (e - 2).abs)
95
86
  end
96
87
  i
97
- when FLOAT
98
- if string =~ /\A[-+]?\.\Z/
99
- @string_cache[string] = true
88
+ elsif string.match?(FLOAT)
89
+ if string.match?(/\A[-+]?\.\Z/)
100
90
  string
101
91
  else
102
92
  Float(string.gsub(/[,_]|\.([Ee]|$)/, '\1'))
103
93
  end
94
+ elsif string.match?(INTEGER)
95
+ parse_int string
104
96
  else
105
- int = parse_int string.gsub(/[,_]/, '')
106
- return int if int
107
-
108
- @string_cache[string] = true
109
97
  string
110
98
  end
111
99
  end
@@ -113,8 +101,7 @@ module Psych
113
101
  ###
114
102
  # Parse and return an int from +string+
115
103
  def parse_int string
116
- return unless INTEGER === string
117
- Integer(string)
104
+ Integer(string.gsub(/[,_]/, ''))
118
105
  end
119
106
 
120
107
  ###
@@ -2,9 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
  module Psych
4
4
  # The version of Psych you are using
5
- VERSION = '3.1.0' unless defined?(::Psych::VERSION)
5
+ VERSION = '3.2.0'
6
6
 
7
7
  if RUBY_ENGINE == 'jruby'
8
- DEFAULT_SNAKEYAML_VERSION = '1.23'.freeze
8
+ DEFAULT_SNAKEYAML_VERSION = '1.26'.freeze
9
9
  end
10
10
  end
@@ -12,34 +12,38 @@ module Psych
12
12
  ###
13
13
  # This class walks a YAML AST, converting each node to Ruby
14
14
  class ToRuby < Psych::Visitors::Visitor
15
- def self.create
15
+ def self.create(symbolize_names: false, freeze: false)
16
16
  class_loader = ClassLoader.new
17
17
  scanner = ScalarScanner.new class_loader
18
- new(scanner, class_loader)
18
+ new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze)
19
19
  end
20
20
 
21
21
  attr_reader :class_loader
22
22
 
23
- def initialize ss, class_loader
23
+ def initialize ss, class_loader, symbolize_names: false, freeze: false
24
24
  super()
25
25
  @st = {}
26
26
  @ss = ss
27
27
  @domain_types = Psych.domain_types
28
28
  @class_loader = class_loader
29
+ @symbolize_names = symbolize_names
30
+ @freeze = freeze
29
31
  end
30
32
 
31
33
  def accept target
32
34
  result = super
33
- return result if @domain_types.empty? || !target.tag
34
35
 
35
- key = target.tag.sub(/^[!\/]*/, '').sub(/(,\d+)\//, '\1:')
36
- key = "tag:#{key}" unless key =~ /^(?:tag:|x-private)/
36
+ unless @domain_types.empty? || !target.tag
37
+ key = target.tag.sub(/^[!\/]*/, '').sub(/(,\d+)\//, '\1:')
38
+ key = "tag:#{key}" unless key =~ /^(?:tag:|x-private)/
37
39
 
38
- if @domain_types.key? key
39
- value, block = @domain_types[key]
40
- return block.call value, result
40
+ if @domain_types.key? key
41
+ value, block = @domain_types[key]
42
+ result = block.call value, result
43
+ end
41
44
  end
42
45
 
46
+ result = deduplicate(result).freeze if @freeze
43
47
  result
44
48
  end
45
49
 
@@ -252,6 +256,8 @@ module Psych
252
256
 
253
257
  e = build_exception((resolve_class($1) || class_loader.exception),
254
258
  h.delete('message'))
259
+
260
+ e.set_backtrace h.delete('backtrace') if h.key? 'backtrace'
255
261
  init_with(e, h, o)
256
262
 
257
263
  when '!set', 'tag:yaml.org,2002:set'
@@ -331,13 +337,12 @@ module Psych
331
337
  list
332
338
  end
333
339
 
334
- SHOVEL = '<<'
335
340
  def revive_hash hash, o
336
341
  o.children.each_slice(2) { |k,v|
337
342
  key = accept(k)
338
343
  val = accept(v)
339
344
 
340
- if key == SHOVEL && k.tag != "tag:yaml.org,2002:str"
345
+ if key == '<<' && k.tag != "tag:yaml.org,2002:str"
341
346
  case v
342
347
  when Nodes::Alias, Nodes::Mapping
343
348
  begin
@@ -359,6 +364,12 @@ module Psych
359
364
  hash[key] = val
360
365
  end
361
366
  else
367
+ if @symbolize_names
368
+ key = key.to_sym
369
+ elsif !@freeze
370
+ key = deduplicate(key)
371
+ end
372
+
362
373
  hash[key] = val
363
374
  end
364
375
 
@@ -366,6 +377,26 @@ module Psych
366
377
  hash
367
378
  end
368
379
 
380
+ if RUBY_VERSION < '2.7'
381
+ def deduplicate key
382
+ if key.is_a?(String)
383
+ # It is important to untaint the string, otherwise it won't
384
+ # be deduplicated into an fstring, but simply frozen.
385
+ -(key.untaint)
386
+ else
387
+ key
388
+ end
389
+ end
390
+ else
391
+ def deduplicate key
392
+ if key.is_a?(String)
393
+ -key
394
+ else
395
+ key
396
+ end
397
+ end
398
+ end
399
+
369
400
  def merge_key hash, key, val
370
401
  end
371
402
 
@@ -181,41 +181,11 @@ module Psych
181
181
  end
182
182
 
183
183
  def visit_Exception o
184
- tag = ['!ruby/exception', o.class.name].join ':'
185
-
186
- @emitter.start_mapping nil, tag, false, Nodes::Mapping::BLOCK
187
-
188
- {
189
- 'message' => private_iv_get(o, 'mesg'),
190
- 'backtrace' => private_iv_get(o, 'backtrace'),
191
- }.each do |k,v|
192
- next unless v
193
- @emitter.scalar k, nil, nil, true, false, Nodes::Scalar::ANY
194
- accept v
195
- end
196
-
197
- dump_ivars o
198
-
199
- @emitter.end_mapping
184
+ dump_exception o, o.message.to_s
200
185
  end
201
186
 
202
187
  def visit_NameError o
203
- tag = ['!ruby/exception', o.class.name].join ':'
204
-
205
- @emitter.start_mapping nil, tag, false, Nodes::Mapping::BLOCK
206
-
207
- {
208
- 'message' => o.message.to_s,
209
- 'backtrace' => private_iv_get(o, 'backtrace'),
210
- }.each do |k,v|
211
- next unless v
212
- @emitter.scalar k, nil, nil, true, false, Nodes::Scalar::ANY
213
- accept v
214
- end
215
-
216
- dump_ivars o
217
-
218
- @emitter.end_mapping
188
+ dump_exception o, o.message.to_s
219
189
  end
220
190
 
221
191
  def visit_Regexp o
@@ -458,15 +428,6 @@ module Psych
458
428
  node = @emitter.start_mapping(nil, tag, false, Psych::Nodes::Mapping::BLOCK)
459
429
  register(o, node)
460
430
 
461
- # Dump the elements
462
- accept 'elements'
463
- @emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
464
- o.each do |k,v|
465
- accept k
466
- accept v
467
- end
468
- @emitter.end_mapping
469
-
470
431
  # Dump the ivars
471
432
  accept 'ivars'
472
433
  @emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
@@ -476,6 +437,15 @@ module Psych
476
437
  end
477
438
  @emitter.end_mapping
478
439
 
440
+ # Dump the elements
441
+ accept 'elements'
442
+ @emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
443
+ o.each do |k,v|
444
+ accept k
445
+ accept v
446
+ end
447
+ @emitter.end_mapping
448
+
479
449
  @emitter.end_mapping
480
450
  else
481
451
  tag = "!ruby/hash:#{o.class}"
@@ -492,6 +462,24 @@ module Psych
492
462
  def dump_list o
493
463
  end
494
464
 
465
+ def dump_exception o, msg
466
+ tag = ['!ruby/exception', o.class.name].join ':'
467
+
468
+ @emitter.start_mapping nil, tag, false, Nodes::Mapping::BLOCK
469
+
470
+ if msg
471
+ @emitter.scalar 'message', nil, nil, true, false, Nodes::Scalar::ANY
472
+ accept msg
473
+ end
474
+
475
+ @emitter.scalar 'backtrace', nil, nil, true, false, Nodes::Scalar::ANY
476
+ accept o.backtrace
477
+
478
+ dump_ivars o
479
+
480
+ @emitter.end_mapping
481
+ end
482
+
495
483
  def format_time time
496
484
  if time.utc?
497
485
  time.strftime("%Y-%m-%d %H:%M:%S.%9N Z")