|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | 3 | require 'json' |
4 | | -require 'strscan' |
5 | 4 |
|
6 | 5 | module JWT |
7 | 6 | # JSON parsing utilities with duplicate key detection support |
@@ -29,101 +28,12 @@ def generate(data) |
29 | 28 | # JWT::JSON.parse('{"a":1,"a":2}', allow_duplicate_keys: false) |
30 | 29 | # # => raises JWT::DuplicateKeyError |
31 | 30 | def parse(data, allow_duplicate_keys: true) |
32 | | - DuplicateKeyChecker.check!(data) unless allow_duplicate_keys |
33 | | - ::JSON.parse(data) |
34 | | - end |
35 | | - end |
36 | | - |
37 | | - # @api private |
38 | | - # Checks for duplicate keys in a JSON string using a StringScanner-based tokenizer |
39 | | - # rubocop:disable Style/RedundantRegexpArgument |
40 | | - class DuplicateKeyChecker |
41 | | - def self.check!(json_str) |
42 | | - new(json_str).check! |
43 | | - end |
44 | | - |
45 | | - def initialize(json_str) |
46 | | - @scanner = StringScanner.new(json_str) |
47 | | - @seen_keys_stack = [[]] |
48 | | - @depth = 0 |
49 | | - @in_array_stack = [false] |
50 | | - end |
51 | | - |
52 | | - def check! |
53 | | - scan_tokens until @scanner.eos? |
54 | | - end |
55 | | - |
56 | | - private |
57 | | - |
58 | | - def scan_tokens # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity |
59 | | - skip_whitespace |
60 | | - return if @scanner.eos? |
61 | | - |
62 | | - if @scanner.scan(/\{/) |
63 | | - handle_object_start |
64 | | - elsif @scanner.scan(/\}/) |
65 | | - handle_container_end |
66 | | - elsif @scanner.scan(/\[/) |
67 | | - handle_array_start |
68 | | - elsif @scanner.scan(/\]/) |
69 | | - @depth -= 1 |
70 | | - elsif @scanner.scan(/,/) || @scanner.scan(/:/) |
71 | | - # skip comma and colon |
72 | | - elsif @scanner.scan(/"/) |
73 | | - handle_string |
74 | | - elsif @scanner.scan(/-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/) |
75 | | - # skip number |
76 | | - elsif @scanner.scan(/true|false|null/) |
77 | | - # skip literal |
78 | | - else |
79 | | - @scanner.getch |
80 | | - end |
81 | | - end |
82 | | - |
83 | | - def skip_whitespace |
84 | | - @scanner.scan(/\s+/) |
85 | | - end |
86 | | - |
87 | | - def handle_object_start |
88 | | - @depth += 1 |
89 | | - @seen_keys_stack[@depth] = [] |
90 | | - @in_array_stack[@depth] = false |
91 | | - end |
92 | | - |
93 | | - def handle_array_start |
94 | | - @depth += 1 |
95 | | - @seen_keys_stack[@depth] = [] |
96 | | - @in_array_stack[@depth] = true |
97 | | - end |
98 | | - |
99 | | - def handle_container_end |
100 | | - @depth -= 1 |
101 | | - end |
102 | | - |
103 | | - def handle_string |
104 | | - str = scan_string_content |
105 | | - check_if_key(str) |
106 | | - end |
107 | | - |
108 | | - def scan_string_content |
109 | | - str = +'' |
110 | | - str << (@scanner.getch || '') until @scanner.scan(/"/) |
111 | | - str |
112 | | - end |
113 | | - |
114 | | - def check_if_key(str) |
115 | | - return if @in_array_stack[@depth] |
116 | | - |
117 | | - pos = @scanner.pos |
118 | | - skip_whitespace |
119 | | - if @scanner.peek(1) == ':' |
120 | | - raise JWT::DuplicateKeyError, "Duplicate key detected: #{str}" if @seen_keys_stack[@depth].include?(str) |
| 31 | + ::JSON.parse(data, allow_duplicate_key: allow_duplicate_keys) |
| 32 | + rescue ::JSON::ParserError => e |
| 33 | + raise JWT::DuplicateKeyError, e.message if e.message.include?('duplicate key') |
121 | 34 |
|
122 | | - @seen_keys_stack[@depth] << str |
123 | | - end |
124 | | - @scanner.pos = pos |
| 35 | + raise |
125 | 36 | end |
126 | 37 | end |
127 | | - # rubocop:enable Style/RedundantRegexpArgument |
128 | 38 | end |
129 | 39 | end |
0 commit comments